web-dev-qa-db-ja.com

ユーザースペースにmmapされたエクスプロイトコードは、カーネルスペースでどのように実行されますか?

プロセスの特権を昇格させてrootアクセスを取得するために悪用される可能性のあるスタックバッファオーバーフローの脆弱性を説明するUbuntu-12.10セキュリティエクスプロイト_CVE-2013-1763_について読んでいます。

ハードコードされたエクスプロイトは次のリンクにあります: https://github.com/spinlockirqsave/examples/blob/master/hacker/ubuntu_cred/main.c

同じことに関していくつか質問があります。

  1. _sdiag_family=0x37_はどのように決定されましたか?
  2. 正確な制御フローとは何ですか?ソケット要求が送信されると、ユーザースペースにmmapされた特権昇格コードx()はどのように実行されますか?
  3. _sdiag_family=0x37_と_mmap_start=0x1a000_の値の間に関係はありますか?
1
bawejakunal

影響を受けるコード (カーネル3.7.0から、115行目から132行目)を見ると:

_static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
        int err;
        struct sock_diag_req *req = nlmsg_data(nlh);
        const struct sock_diag_handler *hndl;

        if (nlmsg_len(nlh) < sizeof(*req))
                return -EINVAL;

        hndl = sock_diag_lock_handler(req->sdiag_family);
        if (hndl == NULL)
                err = -ENOENT;
        else
                err = hndl->dump(skb, nlh);
        sock_diag_unlock_handler(hndl);

        return err;
}
_

sock_diag_lock_handler()関数には、_req->sdiag_family_値をインデックスとして使用してアクセスします。インデックスはユーザーランドから取得されます。呼び出される関数コードは次のとおりです。

_static const inline struct sock_diag_handler *sock_diag_lock_handler(int family)
{
        if (sock_diag_handlers[family] == NULL)
                request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
                                NETLINK_SOCK_DIAG, family);

        mutex_lock(&sock_diag_table_mutex);
        return sock_diag_handlers[family];
}
_

したがって、提供されたインデックスは、配列の長さをチェックせずに、_sock_diag_handlers[]_という配列に直接アクセスするために使用されていることがわかります。配列には40個の要素が含まれていますが、エクスプロイトのインデックスは0x37、つまり55です。したがって、コードは配列の終わりを16エントリ超えたバイトを読み取ります(つまり、各エントリはポインタであり、エクスプロイトは明らかに64ビットマシン)。

戻り値は、呼び出し元(__sock_diag_rcv_msg())によって構造体へのポインターとして使用されます。構造体には、特に、関数ポインターであるdump()と呼ばれるフィールドとコードが含まれます。 ポインタをたどることによってその関数を呼び出します

このエクスプロイトは、カーネルメモリスペース内に、配列のオフセット55(配列の一部ではなく、たまたまそこにある他のデータ)に適切なポインタが存在することに依存しています。そのポインタは、カーネル空間内の良性で正常な構造を指しています。これは、まったく異なる何かを意味します。しかし、障害のあるコードは、その構造を_struct sock_diag_handler_として解釈し、faultフィールドと見なされるものを読み取ります。たまたま値0x1A000が含まれています。したがって、コードはそのアドレスに「ジャンプ」します。

エクスプロイトに残っているのは、0x1A000アドレスに実行可能コードがあることを確認することだけです。そのアドレスはユーザースペースの一部です。ユーザーランドエクスプロイトは、mmap()呼び出しでそのスペースを予約し、その中にコードを入れることができます。ここでは、現在実行中のプロセスを「ルート」にするコードを示します。

理解しておくべき重要な点は、「ユーザースペース」と「カーネルスペース」が共存しているということです。プロセスが実行するとき、メモリをページのコレクションとして「認識」し、その一部は実際のRAMにマップされます。 「カーネルスペース」は、これらのページのサブセットです。マッピングは [〜#〜] mmu [〜#〜] で行われ、アクセス権を強制します。実行するコードに「カーネル特権」ではなく「ユーザー特権」がある場合。ここではルートと非ルートについては説明していません)、「カーネルスペース」としてマークされているページにアクセスしようとすると、例外(segfault)がトリガーされます。プロセスがシステムコール(例:send()呼び出し)を実行すると、プロセスはカーネルスペースにジャンプし、一時的にカーネル特権を取得します。その時点で、すべてのカーネルスペースに到達できるようになります。ただし、ページのマッピングは変更されていません。ユーザーのランディングページはまだそこにあり、重要なことに、ユーザー特権はカーネルページにアクセスするのに十分ではありませんが、カーネル特権はユーザーページにアクセスするのに十分すぎるほどです。したがって、カーネル特権を持つコードが、たまたまユーザースペースにあるコードにジャンプするのを妨げるものは何もありません。これがここで起こることです。

したがって、エクスプロイト制御フローは次のとおりです。

  • プロセスは、アドレス0x1A000に特別なコードを設定します。
  • プロセスは、カーネル特権を使用して、カーネル空間でsend()の実行につながるいくつかのパラメーターを使用して__sock_diag_rcv_msg()システムコールを実行します。
  • その関数は、関数ポインタのように見えるもの(ただしそうではない)を誤って追跡し、アドレス0x1A000のコードにジャンプします。そのコードはユーザースペースにありますが(つまり、完全に正当なmmap()呼び出しを使用してエクスプロイトによって構築されたものです)、カーネル特権で実行されます。
  • そのエクスプロイトコードは、カーネル権限を使用して、実行中のコードの現在の「ID」を変更します。そのID(UID)は、カーネル空間のあるテーブルの値にすぎません。コードにはカーネル特権があるため、UIDを0(ルートを意味する)に変更するだけで済みます。
  • エクスプロイトコードが返されるので、実行は__sock_diag_rcv_msg()に戻ります。これは、手続き全体に非常に満足しており、戻ります。最終的に、システムコールは終了し、ユーザーランドプロセスはユーザー権限で制御を取り戻します。
  • ただし、カーネルはプロセスがルートとして実行されていると見なすようになりました。エクスプロイトコードは、これらの新しく取得した特権でシェルを起動するだけです。

0x37と0x1A000は、エクスプロイトが調整された特定のカーネルバージョンの場合に「うまく機能する」値です。攻撃者は、オーバーフローした配列の128バイト後のカーネル空間に、カーネルへのポインタがあることに気づきました。 -疑似dumpフィールドに、ポインタとして解釈され、通常のユーザーランドmmap()に到達するのに十分低いアドレスを指す値が含まれているスペース構造。ここで説明しているデータ(オーバーフローした配列とそれに続くRAM内の構造)は、「定数データ」セグメントの一部です。これらは、カーネルのコンパイル時に入力された通常の定数データ構造です。したがって、新しいカーネルがコンパイルされると変更される可能性がありますが、影響を受けるカーネルのすべてのインスタンスはその点で同一になります。これが、エクスプロイトがカーネルバージョン(ここでは、64でUbuntu 12.10に付属しているもの)に固有である理由です。ビットモード)が、そのカーネルを使用するすべてのシステムで動作します。

影響を受ける他のカーネルバージョン、または異なるオプションを持つ他のコンパイルでは、_sdiag_family_と_mmap_start_に異なる値が必要になる場合がありますが、適切な構造の条件は難しくなく、他の多くの適切な組み合わせがある可能性があります。 (または、少なくともそこにあった、カーネルはその点で修正され、配列の終わりを超えてアクセスを行わないため、回避されます問題全体。)

2
Thomas Pornin