読者です 読者をやめる 読者になる 読者になる

x64で実行可能なメモリアドレスに対する入出力システムコールを検知してみる

「x64でDynamic ROP + Return-to-vulnによるASLR+DEP+RELRO回避をやってみる」では、leave命令によるstack pivotができない場合でも同一の脆弱性複数回利用することでシェルを起動した。 また、シェルの起動にはlibcバイナリの中身をwrite(2)で読み出し得られたROP gadgetを利用した。 ここでは、ptraceを利用しシステムコールを監視することで、Dynamic ROPの検知をやってみる。

環境

Ubuntu 12.04 LTS 64bit版

$ uname -a
Linux vm-ubuntu64 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:39:31 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 12.04.4 LTS
Release:        12.04
Codename:       precise

$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3

実装

x86でも同様のことが可能であるが、ここでは簡略化のためx64のみを対象とした。

検証

まずは、上のコードをコンパイルする。

$ gcc -o noexecio noexecio.c

「x64でDynamic ROP + Return-to-vulnによるASLR+DEP+RELRO回避をやってみる」と同様の実行ファイルを用意し、エクスプロイトコードを次のように修正する。

-p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE)
+p = Popen(['./noexecio', './a.out'], stdin=PIPE, stdout=PIPE)

修正したコードを実行すると、次のようになる。

$ python exploit.py 100
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6\x06@\x00\x00\x00\x00\x00AAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xe0\x0f`\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xf0\x0f`\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00 \x06@\x00\x00\x00\x00\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP\x05@\x00\x00\x00\x00\x00'
[+] addr_libc_start = 7faf086f46a0
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6\x06@\x00\x00\x00\x00\x00AAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xe0\x0f`\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xa0Fo\x08\xaf\x7f\x00\x00\x00\x00\x16\x00\x00\x00\x00\x00 \x06@\x00\x00\x00\x00\x00AAAAAAAA\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xe8\x0f`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x14`\x00\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00 \x06@\x00\x00\x00\x00\x00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP\x05@\x00\x00\x00\x00\x00'
*** executable io detected ***: addr=7faf086f46a0, len=160000
[+] len(libc_bin) = 0
Traceback (most recent call last):
  File "exploit.py", line 98, in <module>
    p.stdin.write(buf)
IOError: [Errno 32] Broken pipe

libcバイナリの書き出しを検知し、シェルの起動を阻止できていることが確認できた。

実装の概要

DEP(NX)が有効な条件下では通常実行可能なメモリアドレスにreadやwriteが呼ばれないことを利用し、そのような場合を検知する。 具体的な処理は次のようになる。

  • ptraceを使い、指定したコマンドを子プロセスとして起動し監視する
  • readまたはwriteシステムコールが来たら、procfsを参照して読み書きする領域の先頭と末尾が実行可能なメモリアドレスかどうかを調べる
  • 実行可能なメモリアドレスであった場合は強制終了させる

現実的な状況においては、read、writeのほかにsend、sendto、sendmsg、recv、recvfrom、recvmsgについても考える必要がある。 また、通常のプログラムにおいて誤検知が発生しないかどうかについても評価する必要がある。

備考

あくまで実行時におけるROP gadgetの書き出しを検知するものであるため、次のような場合には効果がない。

  • libcバイナリの詳細がわかっている場合
  • 実行ファイル内のgadgetのみを使う場合
  • memcpyなどで実行不可領域にコピーした後、入出力への書き出しを行う場合

関連URL