バッファオーバーフロー入門のためにしたメモ

書き出し

低い分野の脆弱性が理解できていないので実際にやった実験とそれを実況形式で書き示します。

環境

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="14.04, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"

$ uname -a
Linux nori 3.13.0-24-generic #46-Ubuntu SMP Thu Apr 10 19:11:08 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

実験

実験用コード

buf_over.c

#include<stdio.h>
int 
main(int argc,char *argv[])
{
    char buf[64];//※1
    printf("start fgets.\n");
    fgets(buf,128,stdin);//※2
    printf("\nend fgets.\n");
    printf(buf); 
    return 0;
}

3番目のprintf(3)で書式文字列攻撃ができそうだけどここでは確認用なので放置。

※1 : 関数内変数を文字型で64バイト
※2 : fgets(3)で標準入力を受けるも128バイトと大幅なサイズオーバー

観察

Hello Worldなどを読んで若干ではあるがStackの動きやメモリへの書き込みを学んでいるので、ここで少し考察をしながら確認をして行こうと考えます。

[1] buf[64]の確保

main関数内で定義されたbuf[64]は、スタックフレーム上にメモリ空間を確保すると思われる。

おおよそasmでのこの部分で確保しているのであろう。

   0x0804849d <+0>:  push   ebp
   0x0804849e <+1>:  mov    ebp,esp
   0x080484a0 <+3>:  and    esp,0xfffffff0
=> 0x080484a3 <+6>:  sub    esp,0x50 //ここで変数の確保
   0x080484a6 <+9>:  mov    DWORD PTR [esp],0x8048580

実際にフレームを感が思い浮かべてみるとこのような形になるのだろうか。

0x00000000方向
低位アドレス
    +------------------+
    |buffer[64]        |
    +------------------+
    |Saved EBP         |
    +------------------+
    |Return Address    |
    +------------------+
0xffffffff方向
高位アドレス

それでは実施のリターンアドレスはどのように格納されているのだろうか?それを確認しに行こう。

gdb-peda$ where
#0  0x080484ca in main ()
#1  0xf7e2caf3 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6
#2  0x080483c1 in _start ()

バックとレースではこのように表示されている。 それでは実際のスタック領域ではどのようになっているのだろうか。

gdb-peda$ x/64xw $esp
0xffffd680:    0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3
0xffffd690:    0x00000000 0x00c30000 0x00000001 0x08048321
0xffffd6a0:    0xffffd894 0x0000002f 0x0804a000 0x08048542
0xffffd6b0:    0x00000001 0xffffd774 0xffffd77c 0xf7e464ad
0xffffd6c0:    0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000
0xffffd6d0:    0x080484f0 0x00000000 0x00000000 0xf7e2caf3
0xffffd6e0:    0x00000001 0xffffd774 0xffffd77c 0xf7feae6a
0xffffd6f0:    0x00000001 0xffffd774 0xffffd714 0x0804a01c
0xffffd700:    0x08048250 0xf7fc0000 0x00000000 0x00000000
0xffffd710:    0x00000000 0x50781f72 0x6a40bb62 0x00000000
0xffffd720:    0x00000000 0x00000000 0x00000001 0x080483a0
0xffffd730:    0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000
0xffffd740:    0x00000001 0x080483a0 0x00000000 0x080483c1
0xffffd750:    0x0804849d 0x00000001 0xffffd774 0x080484f0
0xffffd760:    0x08048560 0xf7feb300 0xffffd76c 0x0000001c
0xffffd770:    0x00000001 0xffffd894 0x00000000 0xffffd8a9

0xffffd6d0の行で6列目にwhereによって確認された0xf7e2caf3を発見。

つまるところこの位置が目印となるReturn Addressになる。

※ 自分用メモ : sub esp,0x50 でポインタを移動させているだけなのでメモリ内部は初期化されていない。

f:id:oukasakura3:20190128221824p:plain
ローカル変数のメモリ領域を確保するまで

[2] buf[64]への書き込み

実際に書き込みをしてみる。

このままgdbを進めてみると、fgets(3)が作動する。

   0x080484c7 <+42>:    mov    DWORD PTR [esp],eax
   0x080484ca <+45>:    call   0x8048360 <fgets@plt>
=> 0x080484cf <+50>:    mov    DWORD PTR [esp],0x804858d

実際にA*64を入力してみて、64も自分のメモリ空間を埋めてみる。

すると、メモリ領域に変化が見えた。

ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)

ESPでは0xffffd680のアドレスをさしており、そのアドレスが参照しているアドレスが0xffffd690となる。

実際にそのアドレスを見てみると、

gdb-peda$ x/64xw $esp
0xffffd680:    0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3
0xffffd690:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6a0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6b0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6c0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6d0:    0x0804000a 0x00000000 0x00000000 0xf7e2caf3
0xffffd6e0:    0x00000001 0xffffd774 0xffffd77c 0xf7feae6a
0xffffd6f0:    0x00000001 0xffffd774 0xffffd714 0x0804a01c
0xffffd700:    0x08048250 0xf7fc0000 0x00000000 0x00000000
0xffffd710:    0x00000000 0x9ce8c9fd 0xa6d06ded 0x00000000
0xffffd720:    0x00000000 0x00000000 0x00000001 0x080483a0
0xffffd730:    0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000
0xffffd740:    0x00000001 0x080483a0 0x00000000 0x080483c1
0xffffd750:    0x0804849d 0x00000001 0xffffd774 0x080484f0
0xffffd760:    0x08048560 0xf7feb300 0xffffd76c 0x0000001c
0xffffd770:    0x00000001 0xffffd894 0x00000000 0xffffd8a9

そう0x414141....とメモリ空間を埋めている。64文字分しっかりと埋めていた。

この入力を実際に実行ファイルに入力してみる。

$ python -c 'print("A"*64)'|./buf 
start fgets.

end fgets.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

問題なく終了した。それでは、64文字以上の文字列を入れたらどうなるのだろうか?ここで初歩的な疑問にやっとのことたどり着いた。

[3] 過剰な入力

それではどれほど入力してみようか、と私は考える。はじめはできる限り多めの入力をしておこう。

ならば上限である128文字を入力しよう。

$ python -c 'print("A"*128)'| ./buf 
start fgets.

end fgets.
Segmentation fault (コアダンプ)

まぁ、そうなりますはな....

gdbで実際に見てみよう。

gdb-peda$ x/64xw $esp
0xffffd680:    0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3
0xffffd690:    0x00000000 0x00c30000 0x00000001 0x08048321
0xffffd6a0:    0xffffd894 0x0000002f 0x0804a000 0x08048542
0xffffd6b0:    0x00000001 0xffffd774 0xffffd77c 0xf7e464ad
0xffffd6c0:    0xf7fc03c4 0xf7ffd000 0x080484fb 0xf7fc0000
0xffffd6d0:    0x080484f0 0x00000000 0x00000000 0xf7e2caf3
0xffffd6e0:    0x00000001 0xffffd774 0xffffd77c 0xf7feae6a
0xffffd6f0:    0x00000001 0xffffd774 0xffffd714 0x0804a01c
0xffffd700:    0x08048250 0xf7fc0000 0x00000000 0x00000000
0xffffd710:    0x00000000 0x818fa802 0xbbb70c12 0x00000000
0xffffd720:    0x00000000 0x00000000 0x00000001 0x080483a0
0xffffd730:    0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000
0xffffd740:    0x00000001 0x080483a0 0x00000000 0x080483c1
0xffffd750:    0x0804849d 0x00000001 0xffffd774 0x080484f0
0xffffd760:    0x08048560 0xf7feb300 0xffffd76c 0x0000001c
0xffffd770:    0x00000001 0xffffd894 0x00000000 0xffffd8a9
gdb-peda$ n
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[-----------------------------------registers-----------------------------------]
EAX: 0xffffd690 ('A' <repeats 15 times>...)
EBX: 0xf7fc0000 --> 0x1acda8 
ECX: 0xffffd690 ('A' <repeats 15 times>...)
EDX: 0xf7fc18a4 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd6d8 ('A' <repeats 15 times>...)
ESP: 0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)
EIP: 0x80484cf (<main+50>:  mov    DWORD PTR [esp],0x804858d)
[-------------------------------------code--------------------------------------]
   0x80484c3 <main+38>:  lea    eax,[esp+0x10]
   0x80484c7 <main+42>:  mov    DWORD PTR [esp],eax
   0x80484ca <main+45>:  call   0x8048360 <fgets@plt>
=> 0x80484cf <main+50>:  mov    DWORD PTR [esp],0x804858d
   0x80484d6 <main+57>:  call   0x8048370 <puts@plt>
   0x80484db <main+62>:  lea    eax,[esp+0x10]
   0x80484df <main+66>:  mov    DWORD PTR [esp],eax
   0x80484e2 <main+69>:  call   0x8048350 <printf@plt>
[-------------------------------------stack-------------------------------------]
00:0000| esp     0xffffd680 --> 0xffffd690 ('A' <repeats 15 times>...)
01:0004|         0xffffd684 --> 0x80 
02:0008|         0xffffd688 --> 0xf7fc0c20 --> 0xfbad2288 
03:0012|         0xffffd68c --> 0xf7e462f3 (add    ebx,0x179d0d)
04:0016| ecx eax 0xffffd690 ('A' <repeats 15 times>...)
05:0020|         0xffffd694 ('A' <repeats 15 times>...)
06:0024|         0xffffd698 ('A' <repeats 15 times>...)
07:0028|         0xffffd69c ('A' <repeats 15 times>...)
[-------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
0x080484cf in main ()
gdb-peda$ x/64xw $esp
0xffffd680:    0xffffd690 0x00000080 0xf7fc0c20 0xf7e462f3
0xffffd690:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6a0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6b0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6c0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6d0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6e0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd6f0:    0x41414141 0x41414141 0x41414141 0x41414141
0xffffd700:    0x41414141 0x41414141 0x41414141 0x00414141
0xffffd710:    0x00000000 0x818fa802 0xbbb70c12 0x00000000
0xffffd720:    0x00000000 0x00000000 0x00000001 0x080483a0
0xffffd730:    0x00000000 0xf7ff0660 0xf7e2ca09 0xf7ffd000
0xffffd740:    0x00000001 0x080483a0 0x00000000 0x080483c1
0xffffd750:    0x0804849d 0x00000001 0xffffd774 0x080484f0
0xffffd760:    0x08048560 0xf7feb300 0xffffd76c 0x0000001c
0xffffd770:    0x00000001 0xffffd894 0x00000000 0xffffd8a9

圧倒的塗りつぶし!

0x41(A)で塗りつぶされてReturn Address まで塗りつぶされました。

[4] どこでSegmentation fault?

では実際にどこでSegmentation faultが発生したのか?僕としてはそこが気になりました。

gdbを先に進めましょう。

fgets(3)の後にバックとレースをしてみますと、汚染が常に進んでおり、retに0x414141...がたむろする状況。

gdb-peda$ where
#0  0x080484cf in main ()
#1  0x41414141 in ?? ()
#2  0x41414141 in ?? ()
#3  0x41414141 in ?? ()
#4  0x41414141 in ?? ()
#5  0x41414141 in ?? ()
#6  0x41414141 in ?? ()
#7  0x41414141 in ?? ()
#8  0x41414141 in ?? ()
#9  0x41414141 in ?? ()
#10 0x41414141 in ?? ()
#11 0x41414141 in ?? ()
#12 0x41414141 in ?? ()
#13 0x00414141 in ?? ()
#14 0x00000000 in ?? ()

この時点では、#1のリターンアドレス、つまりmain()終了時のret時にSegmentation faultが発生するのではないかと今の所は考えている。

では、進めよう。

実際にその通りなのかもしれない。

[-----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1acda8 
ECX: 0x0 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 (b'AAAA')
ESP: 0xffffd6dc ('A' <repeats 15 times>...)
EIP: 0x80484ed (<main+80>:  ret)
[-------------------------------------code--------------------------------------]
   0x80484e2 <main+69>:  call   0x8048350 <printf@plt>
   0x80484e7 <main+74>:  mov    eax,0x0
   0x80484ec <main+79>:  leave  
=> 0x80484ed <main+80>:  ret    
   0x80484ee:   xchg   ax,ax
   0x80484f0 <__libc_csu_init>:   push   ebp
   0x80484f1 <__libc_csu_init+1>:    push   edi
   0x80484f2 <__libc_csu_init+2>:    xor    edi,edi
[-------------------------------------stack-------------------------------------]
00:0000| esp 0xffffd6dc ('A' <repeats 15 times>...)
01:0004|     0xffffd6e0 ('A' <repeats 15 times>...)
02:0008|     0xffffd6e4 ('A' <repeats 15 times>...)
03:0012|     0xffffd6e8 ('A' <repeats 15 times>...)
04:0016|     0xffffd6ec ('A' <repeats 15 times>...)
05:0020|     0xffffd6f0 ('A' <repeats 15 times>...)
06:0024|     0xffffd6f4 ('A' <repeats 15 times>...)
07:0028|     0xffffd6f8 ('A' <repeats 15 times>...)
[-------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
0x080484ed in main ()
gdb-peda$ where
#0  0x080484ed in main ()
#1  0x41414141 in ?? ()
#2  0x41414141 in ?? ()
#3  0x41414141 in ?? ()
#4  0x41414141 in ?? ()
#5  0x41414141 in ?? ()
#6  0x41414141 in ?? ()
#7  0x41414141 in ?? ()
#8  0x41414141 in ?? ()
#9  0x41414141 in ?? ()
#10 0x41414141 in ?? ()
#11 0x41414141 in ?? ()
#12 0x41414141 in ?? ()
#13 0x00414141 in ?? ()
#14 0x00000000 in ?? ()
gdb-peda$ disas
Dump of assembler code for function main:
   0x0804849d <+0>:  push   ebp
   0x0804849e <+1>:  mov    ebp,esp
   0x080484a0 <+3>:  and    esp,0xfffffff0
   0x080484a3 <+6>:  sub    esp,0x50
   0x080484a6 <+9>:  mov    DWORD PTR [esp],0x8048580
   0x080484ad <+16>: call   0x8048370 <puts@plt>
   0x080484b2 <+21>: mov    eax,ds:0x804a028
   0x080484b7 <+26>: mov    DWORD PTR [esp+0x8],eax
   0x080484bb <+30>: mov    DWORD PTR [esp+0x4],0x80
   0x080484c3 <+38>: lea    eax,[esp+0x10]
   0x080484c7 <+42>: mov    DWORD PTR [esp],eax
   0x080484ca <+45>: call   0x8048360 <fgets@plt>
   0x080484cf <+50>: mov    DWORD PTR [esp],0x804858d
   0x080484d6 <+57>: call   0x8048370 <puts@plt>
   0x080484db <+62>: lea    eax,[esp+0x10]
   0x080484df <+66>: mov    DWORD PTR [esp],eax
   0x080484e2 <+69>: call   0x8048350 <printf@plt>
   0x080484e7 <+74>: mov    eax,0x0
   0x080484ec <+79>: leave  
=> 0x080484ed <+80>: ret    
End of assembler dump.

retに至った時点で常にebpが0x414141...となっておりこのまま進めてもSegmentation faultとなるであろう。

その後実際にstep inで実行してみると、

gdb-peda$ si
[-----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1acda8 
ECX: 0x0 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 (b'AAAA')
ESP: 0xffffd6e0 ('A' <repeats 15 times>...)
EIP: 0x41414141 (b'AAAA')
[-------------------------------------code--------------------------------------]
Invalid $PC address: 0x41414141
[-------------------------------------stack-------------------------------------]
00:0000| esp 0xffffd6e0 ('A' <repeats 15 times>...)
01:0004|     0xffffd6e4 ('A' <repeats 15 times>...)
02:0008|     0xffffd6e8 ('A' <repeats 15 times>...)
03:0012|     0xffffd6ec ('A' <repeats 15 times>...)
04:0016|     0xffffd6f0 ('A' <repeats 15 times>...)
05:0020|     0xffffd6f4 ('A' <repeats 15 times>...)
06:0024|     0xffffd6f8 ('A' <repeats 15 times>...)
07:0028|     0xffffd6fc ('A' <repeats 15 times>...)
[-------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
0x41414141 in ?? ()

となっておりEIPの汚染を確実に可能とした。

結論

スタック状に格納されるローカル変数を汚染するための方法を理解できたと思う。

次はもう少し攻撃的なものを覚えたい。

参考書籍/サイト

ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ Debugging with GDB - データの検査 セキュリティコンテストチャレンジブック -CTFで学ぼう! 情報を守るための戦い方