スマートフォン解析 top

TOP > タイガーチームセキュリティレポート > Buffer Overflow脆弱性の動的パッチの方法 - Part 2

タイガーチームセキュリティレポート

Buffer Overflow脆弱性の動的パッチの方法 - Part 2

今回のコラムではBuffer Overflow脆弱性の動的パッチを実装するための準備を行います。今回の内容はおもにSystemTapというツールの紹介です。


-SystemTapの紹介-

SystemTapはもともとLinux Kernelを動的にデバッグするためのツールです。かつてはKernelの動作を解析するためにわざわざprintk()などを埋め込んでKernelを再コンパイルしマシンを再起動する必要があったわけですが、SystemTapを使えばその必要はありません。

Kernel空間のいたるところ(probe point)にユーザが定義した処理(probe handler)をフックさせることができます。ユーザはprobe handlerをCに似た文法のスクリプト言語で記述できます。また後の例で示すようにスクリプトにCで記述した処理を埋め込むことも可能です。

いずれにせよSystemTapスクリプトはすべてCのソースに変換されたあとにLKM(Linux Kernel Module)にコンパイルされてロードされますが、SystemTapはLKMをCだけで記述するときの煩わしさから解放してくれます。:-)

もともとSystemTapはLinux Kernelを解析するためのツールでしたが、のちにUser空間のプロセスを解析できるように拡張されました。次回のコラムではUser空間のプロセスにprobe handlerをフックするためにSystemTapを使用します。

ちなみにSolarisやFreeBSDにはDTraceという似たようなツールがあります。

-SystemTap超入門-

細かいことはドキュメントを読んでいただくこととして必要最低限のことだけをまとめます。

SystemTapスクリプトは以下の形式で定義されるprobe handlerの集まりです。多くのプログラミング言語のように頭から処理を記述する必要はありません。ユーザは興味のあるイベント(probe point)に対して何らかの処理(probe handler)を定義すれば良いだけです。

probe probe point
{
	probe handler
}
probe handlerの定義

SystemTapでは関数を定義して処理をまとめることができます。また%{と%}で括って間にCで記述した処理を埋め込むこともできます。

通常のSystemTapのモードでは安全に実行されるように機能がかなり制限されています。%{と%}が使えるSystemTapのモードはguru modeと呼ばれます。guru modeでは容易にKernel panicなどを引き起こすのでCでLKMを記述する場合と同様に注意深くなる必要があります。

-SystemTapの実例-

ひとつの実例を紹介します。

SystemTapを使ってNmapの動作を解析してみましょう。NmapはHackerからScript kiddieのみならず一部のシステム管理者にも愛好されるポートスキャナーであり、われわれのようなセキュリティ検査にたずさわるものにとっても有用なツールのひとつです。

Nmapにはサービススキャンという機能があります。これはポートを開いているサービスに対してさまざまなパターンのパケットを投げてサービスからの応答からサービスの種類やバージョンを判定する機能です。ここではSystmTapを使ってNmapがサービススキャンの際にネットワーク上でどのような入出力を行っているか可視化してみます。

同じことを行う方法はほかにもいろいろあるのですが、今回はあえてSystemTapを使ってみました。ちょっと長いですが実際のコードを示します。

#!/usr/bin/stap
%{
#include <linux/fs.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/sched.h>
#include <linux/socket.h>
#include <linux/uio.h>
%}
function print_sendmsg:long(msg:long, sock:long)
%{
    int i, j, k;
    int cpy_len, addr_len, err;
    unsigned char buff[16];
    char src[16], dst[16];
    struct sockaddr_in addr;
    struct msghdr *msg = (struct msghdr *)THIS->msg;
    struct socket *sock = (struct socket *)THIS->sock;
    if(sock->type != SOCK_STREAM && sock->type != SOCK_DGRAM)
        return;
    for (i = 0; i< msg->msg_iovlen; i++) {
        struct iovec *msg_iov = msg->msg_iov + i;
        void *iov_base = msg_iov->iov_base;
        size_t iov_len = msg_iov->iov_len;
        _stp_printf("%s msg ", sock->type == SOCK_STREAM ? "TCP" : "UDP");
        err = sock->ops->getname(sock, (struct sockaddr *)&addr, &addr_len, 0);
        if (!err) {
            switch (addr.sin_family) {
                case AF_INET:
                    snprintf(src, 16, "%pI4", &addr.sin_addr);
                    _stp_printf("[%s:%hu --> ", src, ntohs(addr.sin_port));
                    break;
                case AF_INET6:
                    break;
                default:
                    break;
            }
        }
        err = sock->ops->getname(sock, (struct sockaddr *)&addr, &addr_len, 1);
        if (!err) {
            switch (addr.sin_family) {
                case AF_INET:
                    snprintf(dst, 16, "%pI4", &addr.sin_addr);
                    _stp_printf("%s:%hu] (%d bytes)", dst, ntohs(addr.sin_port), iov_len);
                    break;
                case AF_INET6:
                    break;
                default:
                    break;
            }
        }
        _stp_printf("\n");
        j = 0, cpy_len = 0;
        while (j < iov_len) {
            cpy_len = (iov_len - j > 16) ? 16 : iov_len - j;
            if (copy_from_user(buff, iov_base + j, cpy_len) != 0) {
                _stp_printf("error copying from user space\n");
            }
            for (k = 0; k < 16; k++) {
                if (k < cpy_len) {
                    _stp_printf(" %02x", buff[k]);
                } else {
                    _stp_printf("   ");
                }
                if (k == 7)
                    _stp_printf(" ");
            }
            _stp_printf(" |");
            for (k = 0; k < 16; k++) {
                if (k < cpy_len && buff[k] > 31 && buff[k] < 127) {
                    _stp_printf("%c", buff[k]);
                } else if (k < cpy_len) {
                    _stp_printf(".");
                } else {
                    _stp_printf(" ");
                }
            }
            _stp_printf("|\n");
            j += cpy_len;
        }
    }
%}
function print_recvmsg:long(msg:long, sock:long, len:long)
%{
    int i, j, k;
    int cpy_len, addr_len, err;
    unsigned char buff[16];
    char src[16], dst[16];
    struct sockaddr_in addr;
    void *ubuff;
    struct msghdr *msg;
    struct socket *sock;
    long len;
    msg = (struct msghdr *)THIS->msg;
    sock = (struct socket *)THIS->sock;
    len = THIS->len;
    if(sock->type != SOCK_STREAM && sock->type != SOCK_DGRAM)
        return;
    _stp_printf("%s msg ", sock->type == SOCK_STREAM ? "TCP" : "UDP");
    err = sock->ops->getname(sock, (struct sockaddr *)&addr, &addr_len, 0);
    if (!err) {
        switch (addr.sin_family) {
            case AF_INET:
                snprintf(src, 16, "%pI4", &addr.sin_addr);
                _stp_printf("[%s:%hu <-- ", src, ntohs(addr.sin_port));
                break;
            case AF_INET6:
                break;
            default:
                break;
        }
    }
    err = sock->ops->getname(sock, (struct sockaddr *)&addr, &addr_len, 1);
    if (!err) {
        switch (addr.sin_family) {
            case AF_INET:
                snprintf(dst, 16, "%pI4", &addr.sin_addr);
                _stp_printf("%s:%hu] (%d bytes)", dst, ntohs(addr.sin_port), len);
                break;
            case AF_INET6:
                break;
            default:
                break;
        }
    }
    _stp_printf("\n");
    ubuff = msg->msg_iov->iov_base - len;
    j = 0, cpy_len = 0;
    while (j < len) {
        cpy_len = (len - j > 16) ? 16 : len - j;
        if (copy_from_user(buff, ubuff + j, cpy_len) != 0) {
            _stp_printf("error copying from user space\n");
        }
        for (k = 0; k < 16; k++) {
            if (k < cpy_len) {
                _stp_printf(" %02x", buff[k]);
            } else {
                _stp_printf("   ");
            }
            if (k == 7)
                _stp_printf(" ");
        }
        _stp_printf(" |");
        for (k = 0; k < 16; k++) {
            if (k < cpy_len && buff[k] > 31 && buff[k] < 127) {
                _stp_printf("%c", buff[k]);
            } else if (k < cpy_len) {
                _stp_printf(".");
            } else {
                _stp_printf(" ");
            }
        }
        _stp_printf("|\n");
        j += cpy_len;
    }
%}
probe kernel.function("sock_sendmsg@net/socket.c") {
    if(execname() == @1) {
        print_sendmsg($msg, $sock);
    }
}
probe kernel.function("sock_recvmsg@net/socket.c").return {
    if(execname() == @1 && $return > 0) {
        print_recvmsg($msg, $sock, $return);
    }
}
trace_msg.stp

このスクリプトには2つのprobe handlerと2つの関数の定義が含まれます。

末尾でLinux Kernelのnet/socket.cで定義された2つの関数sock_sendmsg()とsock_recvmsg()をprobe pointとするprobe handlerを定義しています。スクリプトの第一引数で指定したプログラムがシステムコールsendmsg()とrecvmsg()をコールした場合関数print_sendmsg()とprint_recvmsg()を呼び出します。

Cで記述された2つの関数print_sendmsg()とprint_recvmsg()はsendmsg()システムコールとrecvmsg()システムコールで送受信されるデータをUser空間のバッファからコピーした上でhexdumpに似た形式で端末に出力します。IPv4のみ対応しています。IPv6対応は実装しかけてやめました。

以下にこのスクリプトを使ってDebian wheezy上でnmapが送受信するデータをダンプする方法を示します。まず、端末A上でスクリプトをguruモード(-g)で起動しておきます。

root@debian:~/work/stap# stap -g trace_msg.stp nmap

つぎに、別の端末B上でwww.tiger1997.jpのtcp 80番ポートに対してnmapのサービススキャンを実行してみます。サービスのバージョン『Apache httpd』が取得できました。

alice@debian:~$ sudo nmap -n -v -sV -p 80 www.tiger1997.jp
 
[sudo] password for alice:
Starting Nmap 6.00 ( http://nmap.org ) at 2014-03-18 14:19 JST
NSE: Loaded 17 scripts for scanning.
Initiating Ping Scan at 14:19
Scanning www.tiger1997.jp (210.255.186.91) [4 ports]
Completed Ping Scan at 14:19, 0.02s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 14:19
Scanning www.tiger1997.jp (210.255.186.91) [1 port]
Discovered open port 80/tcp on 210.255.186.91
Completed SYN Stealth Scan at 14:19, 0.01s elapsed (1 total ports)
Initiating Service scan at 14:19
Scanning 1 service on www.tiger1997.jp (210.255.186.91)
Completed Service scan at 14:19, 6.02s elapsed (1 service on 1 host)
NSE: Script scanning 210.255.186.91.
Nmap scan report for www.tiger1997.jp (210.255.186.91)
Host is up (0.0086s latency).
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.56 seconds
           Raw packets sent: 5 (196B) | Rcvd: 2 (88B)
alice@debian:~$

一方、端末A上にはスクリプトの出力結果が表示されました。nmapはサーバに『GET / HTTP/1.0』というリクエストを送りレスポンスのServerヘッダからサーバのバナー情報を抽出したことがわかります。

root@debian:~/work/stap# stap -g trace_msg.stp nmap
UDP msg [192.168.0.10:40592 --> 8.8.8.8:53] (34 bytes)
 df 67 01 00 00 01 00 00  00 00 00 00 03 77 77 77 |.g...........www|
 09 74 69 67 65 72 31 39  39 37 02 6a 70 00 00 01 |.tiger1997.jp...|
 00 01                                            |..              |
UDP msg [192.168.0.10:40592 <-- 8.8.8.8:53] (50 bytes)
 df 67 81 80 00 01 00 01  00 00 00 00 03 77 77 77 |.g...........www|
 09 74 69 67 65 72 31 39  39 37 02 6a 70 00 00 01 |.tiger1997.jp...|
 00 01 c0 0c 00 01 00 01  00 00 54 5f 00 04 d2 ff |..........T_....|
 ba 5b                                            |.[              |
TCP msg [192.168.0.10:47942 --> 210.255.186.91:80] (18 bytes)
 47 45 54 20 2f 20 48 54  54 50 2f 31 2e 30 0d 0a |GET / HTTP/1.0..|
 0d 0a                                            |..              |
TCP msg [192.168.0.10:47942 <-- 210.255.186.91:80] (1292 bytes)
 48 54 54 50 2f 31 2e 31  20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
 0a 44 61 74 65 3a 20 54  75 65 2c 20 31 38 20 4d |.Date: Tue, 18 M|
 61 72 20 32 30 31 34 20  30 35 3a 31 39 3a 30 37 |ar 2014 05:19:07|
 20 47 4d 54 0d 0a 53 65  72 76 65 72 3a 20 41 70 | GMT..Server: Ap|
 61 63 68 65 0d 0a 4c 61  73 74 2d 4d 6f 64 69 66 |ache..Last-Modif|
 69 65 64 3a 20 54 75 65  2c 20 32 35 20 46 65 62 |ied: Tue, 25 Feb|
 20 32 30 31 34 20 30 34  3a 30 37 3a 35 31 20 47 | 2014 04:07:51 G|
 4d 54 0d 0a 45 54 61 67  3a 20 22 36 31 61 30 39 |MT..ETag: "61a09|
 2d 66 32 32 65 2d 34 66  33 33 33 33 62 35 36 36 |-f22e-4f3333b566|
 66 35 63 22 0d 0a 41 63  63 65 70 74 2d 52 61 6e |f5c"..Accept-Ran|
 67 65 73 3a 20 62 79 74  65 73 0d 0a 43 6f 6e 74 |ges: bytes..Cont|
 65 6e 74 2d 4c 65 6e 67  74 68 3a 20 36 31 39 39 |ent-Length: 6199|
 38 0d 0a 56 61 72 79 3a  20 41 63 63 65 70 74 2d |8..Vary: Accept-|
 45 6e 63 6f 64 69 6e 67  0d 0a 43 6f 6e 6e 65 63 |Encoding..Connec|
 74 69 6f 6e 3a 20 63 6c  6f 73 65 0d 0a 43 6f 6e |tion: close..Con|
 74 65 6e 74 2d 54 79 70  65 3a 20 74 65 78 74 2f |tent-Type: text/|
 68 74 6d 6c 3b 20 63 68  61 72 73 65 74 3d 55 54 |html; charset=UT|
 46 2d 38 0d 0a 0d 0a 3c  21 44 4f 43 54 59 50 45 |F-8....<!DOCTYPE|
 20 48 54 4d 4c 20 50 55  42 4c 49 43 20 22 2d 2f | HTML PUBLIC "-/|
 2f 57 33 43 2f 2f 44 54  44 20 48 54 4d 4c 20 34 |/W3C//DTD HTML 4|
 2e 30 31 20 54 72 61 6e  73 69 74 69 6f 6e 61 6c |.01 Transitional|
 2f 2f 45 4e 22 20 22 68  74 74 70 3a 2f 2f 77 77 |//EN" "http://ww|
 77 2e 77 33 2e 6f 72 67  2f 54 52 2f 68 74 6d 6c |w.w3.org/TR/html|
 34 2f 6c 6f 6f 73 65 2e  64 74 64 22 3e 0a 3c 68 |4/loose.dtd">.<h|
 74 6d 6c 3e 0a 3c 68 65  61 64 3e 0a 3c 6d 65 74 |tml>.<head>.<met|
 61 20 68 74 74 70 2d 65  71 75 69 76 3d 22 43 6f |a http-equiv="Co|
 6e 74 65 6e 74 2d 54 79  70 65 22 20 63 6f 6e 74 |ntent-Type" cont|
 65 6e 74 3d 22 74 65 78  74 2f 68 74 6d 6c 3b 20 |ent="text/html; |
 63 68 61 72 73 65 74 3d  75 74 66 2d 38 22 20 2f |charset=utf-8" /|
 3e 0a 3c 74 69 74 6c 65  3e e3 82 b0 e3 83 ad e3 |>.<title>.......|
 83 bc e3 83 90 e3 83 ab  e3 82 bb e3 82 ad e3 83 |................|
 a5 e3 83 aa e3 83 86 e3  82 a3 e3 82 a8 e3 82 ad |................|
 e3 82 b9 e3 83 91 e3 83  bc e3 83 88 e6 a0 aa e5 |................|
 bc 8f e4 bc 9a e7 a4 be  20 7c 20 47 53 58 3c 2f |........ | GSX</|
 74 69 74 6c 65 3e 0a 3c  6d 65 74 61 20 6e 61 6d |title>.<meta nam|
 65 3d 22 64 65 73 63 72  69 70 74 69 6f 6e 22 20 |e="description" |
 63 6f 6e 74 65 6e 74 3d  22 47 53 58 e3 81 af e4 |content="GSX....|
 bc 81 e6 a5 ad e3 82 84  e5 ae 98 e5 85 ac e5 ba |................|
 81 e8 87 aa e6 b2 bb e4  bd 93 e3 81 8c e7 9b b4 |................|
 e9 9d a2 e3 81 97 e3 81  a6 e3 81 84 e3 82 8b e6 |................|
 83 85 e5 a0 b1 e3 82 bb  e3 82 ad e3 83 a5 e3 83 |................|
 aa e3 83 86 e3 82 a3 e3  80 81 e3 82 b3 e3 83 b3 |................|
 e3 83 97 e3 83 a9 e3 82  a4 e3 82 a2 e3 83 b3 e3 |................|
 82 b9 e3 81 ab e5 af be  e3 81 99 e3 82 8b e8 aa |................|
 b2 e9 a1 8c e3 82 92 e3  81 8a e5 ae a2 e6 a7 98 |................|
 e3 81 a8 e4 b8 80 e7 b7  92 e3 81 ab e3 81 aa e3 |................|
 81 a3 e3 81 a6 e8 a7 a3  e6 b1 ba e3 81 84 e3 81 |................|
 9f e3 81 97 e3 81 be e3  81 99 e3 80 82 e3 81 8a |................|
 e6 b0 97 e8 bb bd e3 81  ab e3 81 94 e7 9b b8 e8 |................|
 ab 87 e3 81 8f e3 81 a0  e3 81 95 e3 81 84 e3 80 |................|
 82 22 20 2f 3e 0a 3c 6d  65 74 61 20 6e 61 6d 65 |." />.<meta name|
 3d 22 6b 65 79 77 6f 72  64 73 22 20 63 6f 6e 74 |="keywords" cont|
 65 6e 74 3d 22 47 53 58  2c 67 73 78 2c e3 82 b0 |ent="GSX,gsx,...|
 e3 83 ad e3 83 bc e3 83  90 e3 83 ab e3 82 bb e3 |................|
 82 ad e3 83 a5 e3 83 aa  e3 83 86 e3 82 a3 e3 82 |................|
 a8 e3 82 ad e3 82 b9 e3  83 91 e3 83 bc e3 83 88 |................|
 2c e6 a8 99 e7 9a 84 e5  9e 8b e6 94 bb e6 92 83 |,...............|
 2c e6 b0 b4 e9 a3 b2 e3  81 bf e5 a0 b4 e5 9e 8b |,...............|
 e6 94 bb e6 92 83 2c e3  83 91 e3 82 b9 e3 83 af |......,.........|
 e3 83 bc e3 83 89 e3 83  aa e3 82 b9 e3 83 88 e6 |................|
 94 bb e6 92 83 2c e3 83  aa e3 82 b9 e3 83 88 e5 |.....,..........|
 9e 8b e3 82 a2 e3 82 ab  e3 82 a6 e3 83 b3 e3 83 |................|
 88 e3 83 8f e3 83 83 e3  82 ad e3 83 b3 e3 82 b0 |................|
 2c e3 83 a1 e3 83 bc e3  83 ab e8 a8 93 e7 b7 b4 |,...............|
 2c e6 a8 99 e7 9a 84 e5  9e 8b e3 83 a1 e3 83 bc |,...............|
 e3 83 ab e8 a8 93 e7 b7  b4 e3 82 b5 e3 83 bc e3 |................|
 83 93 e3 82 b9 2c e3 82  a2 e3 83 a9 e3 83 bc e3 |.....,..........|
 83 88 e8 a7 a3 e6 9e 90  e3 82 b5 e3 83 bc e3 83 |................|
 93 e3 82 b9 2c e3 82 bb  e3 82 ad e3 83 a5 e3 83 |....,...........|
 aa e3 83 86 e3 82 a3 e3  82 a4 e3 83 b3 e3 82 b7 |................|
 e3 83 87 e3 83 b3 e3 83  88 2c e3 82 bb e3 82 ad |.........,......|
 e3 83 a5 e3 83 aa e3 83  86 e3 82 a3 e7 b7 8a e6 |................|
 80 a5 e3 82 b5 e3 83 bc  e3 83 93 e3 82 b9 2c e3 |..............,.|
 82 bf e3 82 a4 e3 82 ac  e3 83 bc e3 83 81 e3 83 |................|
 bc e3 83 a0 e3 82 b5 e3  83 bc e3 83 93 e3 82 b9 |................|
 2c e8 84 86 e5 bc b1 e6  80 a7 e8 a8 ba e6 96 ad |,...............|
 2c e3 82 bb e3 82 ad e3  83 a5 e3 83 aa e3 83 86 |,...............|
 e3 82 a3 e7 9b a3 e8 a6  96 2c e3 82 b7 e3 82 b9 |.........,......|
 e3 83 86 e3 83 a0 e7 9b  a3 e6 9f bb 2c e5 a4 96 |............,...|
 e9 83 a8 e7 9b a3 e6 9f  bb 2c e3 82 a4 e3 83 bc |.........,......|
 e3 82 b0 e3 83 ab e3 83  81 e3 83 bc             |............    |

ここでは1つのHTTPリクエスト『GET / HTTP/1.0』だけでバージョン情報が取得できましたが、バージョン情報が取れない場合nmapはバージョン情報が取れるまでHTTP以外にいろいろなパターンのリクエストを試して情報を取得しようとします。Web以外のサービスにスキャンをかけて同じ方法でデータを可視化してみるとnmapが何を送っているかわかると思います。

※許可のないサーバに対して不用意にポートスキャンをかける行為は攻撃とみなされる場合があるので注意してください。

今回のコラムではSystemTapという強力なツールを紹介し、ネットワーク解析への応用例の1つを示しました。次回はSystemTapを使ってbuffer overflowに対する攻撃を防御することを考えてみます。


参考情報:
[1] SystemTap
http://sourceware.org/systemtap/
[2] SystemTap Beginners Guide
https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/SystemTap_Beginners_Guide/index.html
[3] SystemTap - Wikipedia, the free encyclopedia
http://en.wikipedia.org/wiki/Systemtap
[4] DTrace - Wikipedia, the free encyclopedia
http://ja.wikipedia.org/wiki/DTrace


タイガーチームメンバー 塚本 泰三