블로그 이전했습니다. https://jeongzero.oopy.io/
prepare_kernel_cred(), commit_creds() 함수란?
본문 바로가기
컴퓨터 관련 과목/운영체제 & 커널

prepare_kernel_cred(), commit_creds() 함수란?

728x90

Kernel Exploit시 반드시 알아야 하는 기본적인 함수는 prepare_kernel_cred(), commit_creds() 함수이다. 해당 함수를 이용해서 권한 상승을 일으키는 커널 모듈을 만들어보자.

1. cred 구조체


우선 위 2개의 함수를 알기전에 cred 구조체에 대해서 알아야한다. 예전에 리눅스 커널 기초 이론을 정리할때 PCB에 대한 설명을 했다. PCB란 프로세스의 모든 메타데이터 정보를 가지고 있으며, 실제 코드 내부에서 task_struct 구조체로 관리가 된다. 자세한 설명은 아래 글을 참조하면 된다.

kernel of linux - Process Management(1)
요번 강의는 저번 시간에 설명한 fork() 내용의 복습과, PCB의 상세 내용 그리고 clone 에 대해서 설명한다. 저번 정리에서 충분히 설명을 했으므로 간단하게만 정리하겠다. 커널안에 internal 한 함수들이 있는데 그 중 하나가 context_swtch() 함수이다. 해당 함수가 커널 내에서 언제 호출이 되냐면, read, wait, exit 같은 시스템콜이 호출될때 내부적으로 호출이된다.
https://wogh8732.tistory.com/285

위 글을 작성했을때에는 cred 구조체에 대한 부분이 없다. 아마 참조한 강의 영상에서 다루는 강의자료는 예전 커널 소스를 기준으로 했거나 생략된 것으로 보인다. 따라서 실제 내가 테스트하는 우분투 18.04 의 cred 구조체 선언부분을 확인해보자.

/lib/modules/5.4.0-53-generic/build/include/linux/sched.h

...

 848 
 849 #ifdef CONFIG_NO_HZ_FULL
 850     atomic_t            tick_dep_mask;
 851 #endif
 852     /* Context switch counts: */
 853     unsigned long           nvcsw;
 854     unsigned long           nivcsw;
 855 
 856     /* Monotonic time in nsecs: */
 857     u64             start_time;
 858 
 859     /* Boot based time in nsecs: */
 860     u64             real_start_time;
 861 
 862     /* MM fault and swap info: this can arguably be seen as either mm-specific or thread-specifi
 863     unsigned long           min_flt;
 864     unsigned long           maj_flt;
 865 
 866     /* Empty if CONFIG_POSIX_CPUTIMERS=n */
 867     struct posix_cputimers      posix_cputimers;
 868 
 869     /* Process credentials: */
 870 
 871     /* Tracer's credentials at attach: */
 872     const struct cred __rcu     *ptracer_cred;
 873 
 874     /* Objective and real subjective task credentials (COW): */
 875     const struct cred __rcu     *real_cred;
 876 
 877     /* Effective (overridable) subjective task credentials (COW): */
 878     const struct cred __rcu     *cred;
 879 
 880 #ifdef CONFIG_KEYS
 881     /* Cached requested key. */
 882     struct key          *cached_requested_key;
 883 #endif
 884 
 885     /*
 886      * executable name, excluding path.
 887      *
 888      * - normally initialized setup_new_exec()
 889      * - access it with [gs]et_task_comm()
 890      * - lock it with task_lock()
 891      */
 892     char                comm[TASK_COMM_LEN];
 893 
 894     struct nameidata        *nameidata;
 895 
 896 #ifdef CONFIG_SYSVIPC

...

sched.h 헤더파일을 살펴보면 task_struct 를 찾을수 있고 cred 구조체 필드를 확인할 수 있다. cred 구조체는 현재 태스크의 신원 정보를 가리키는 포인터이다. cred __rcu 구조체를 확인해보자.

/lib/modules/5.4.0-53-generic/build/include/linux/cred.h

111 struct cred {
112     atomic_t    usage;
113 #ifdef CONFIG_DEBUG_CREDENTIALS
114     atomic_t    subscribers;    /* number of processes subscribed */
115     void        *put_addr;
116     unsigned    magic;
117 #define CRED_MAGIC  0x43736564
118 #define CRED_MAGIC_DEAD 0x44656144
119 #endif
120     kuid_t      uid;        /* real UID of the task */
121     kgid_t      gid;        /* real GID of the task */
122     kuid_t      suid;       /* saved UID of the task */
123     kgid_t      sgid;       /* saved GID of the task */
124     kuid_t      euid;       /* effective UID of the task */
125     kgid_t      egid;       /* effective GID of the task */
126     kuid_t      fsuid;      /* UID for VFS ops */
127     kgid_t      fsgid;      /* GID for VFS ops */
128     unsigned    securebits; /* SUID-less security management */
129     kernel_cap_t    cap_inheritable; /* caps our children can inherit */
130     kernel_cap_t    cap_permitted;  /* caps we're permitted */
...
145     struct user_struct *user;   /* real user ID subscription */
146     struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
147     struct group_info *group_info;  /* supplementary groups for euid/fsgid */
148     /* RCU deletion */
149     union {
150         int non_rcu;            /* Can we skip RCU deletion? */
151         struct rcu_head rcu;        /* RCU deletion hook */
152     };
153 } __randomize_layout;

중요 필드들은 다음과 같다

  1. usage

    cred 참조 카운터로 하나의 cred 구조체는 여러 개의 프로세스에서 동시에 사용될 수 있다. cred 구조체는 아마 공유자원 인듯?

  1. uid

    현 프로세스를 소유하고 있는 사용자의 ID를 저장한다. id=0 이면 root이다.

  1. euid

    실제 명령을 수행하는 주체의 id를 가리킨다. 사용자가 처음 user1에 로그인을 하면 uid, euid가 동일하지만, su로 user2로 전환하면 uid는 user1이지만, euid는 user2를 가리킨다.

  1. gid, egid

    uid와 euid 같은 방식으로 사용자가 속한 그룹을 나타낸다.

자 이제 cred 구조체에 대한 개념을 가지고 prepare_kernel_cred() 함수를 알아보자.

2. prepare_kernel_cred() 함수


prepare_kernel_cred() 함수와 commit_creds() 함수는 태스크의 권한을 수정하기 위해 커널에서 사용하는 함수이다. 그 중 prepare_kernel_cred() 함수에 대해서 먼저 알아보자.

prepare_kernel_cred() 함수는 원하는 신원 정보의 cred 구조체를 생성하는 함수이다. 즉 현재 커널 서비스에 대해서 자격 증명을 준비하는 과정이다. 권한 설정을 위한 세팅이라고 보면 될것 같다. 함수 원형은 다음과 같다

(cred.c 가 왜 없지? 음 이건 물어봐야겠다.) cred.c 소스코드는 아래 사이트에서 확인 할수 있다.

https://elixir.bootlin.com/linux/v5.4.53/source/kernel/cred.c

struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
	const struct cred *old;
	struct cred *new;

	new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
	if (!new)
		return NULL;

	kdebug("prepare_kernel_cred() alloc %p", new);

	if (daemon)
		old = get_task_cred(daemon);
	else
		old = get_cred(&init_cred);

	validate_creds(old);

	*new = *old;
	new->non_rcu = 0;
	atomic_set(&new->usage, 1);
	set_cred_subscribers(new, 0);
	get_uid(new->user);
	get_user_ns(new->user_ns);
	get_group_info(new->group_info);

#ifdef CONFIG_KEYS
	new->session_keyring = NULL;
	new->process_keyring = NULL;
	new->thread_keyring = NULL;
	new->request_key_auth = NULL;
	new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif

#ifdef CONFIG_SECURITY
	new->security = NULL;
#endif
	if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
		goto error;

	put_cred(old);
	validate_creds(new);
	return new;

error:
	put_cred(new);
	put_cred(old);
	return NULL;
}

함수 인자로 전달된 task_struct 구조체 타입의 daemon 포인터의 값을 확인하여 cred 구조체 변수인 old를 할당 받는다. NULL이 아니면 현재 daemon의 자격증명을 가져오고, 아니면 get_cred(&init_cred)를 호출하여 old에 할당받는다.

즉 init_cred의 자격증명을 가져오는데 이는 root 권한의 자격증명을 가지고 있다.

struct cred init_cred = {
	.usage			= ATOMIC_INIT(4),
#ifdef CONFIG_DEBUG_CREDENTIALS
	.subscribers		= ATOMIC_INIT(2),
	.magic			= CRED_MAGIC,
#endif
	.uid			= GLOBAL_ROOT_UID,
	.gid			= GLOBAL_ROOT_GID,
	.suid			= GLOBAL_ROOT_UID,
	.sgid			= GLOBAL_ROOT_GID,
	.euid			= GLOBAL_ROOT_UID,
	.egid			= GLOBAL_ROOT_GID,
	.fsuid			= GLOBAL_ROOT_UID,
	.fsgid			= GLOBAL_ROOT_GID,
	.securebits		= SECUREBITS_DEFAULT,
	.cap_inheritable	= CAP_EMPTY_SET,
	.cap_permitted		= CAP_FULL_SET,
	.cap_effective		= CAP_FULL_SET,
	.cap_bset		= CAP_FULL_SET,
	.user			= INIT_USER,
	.user_ns		= &init_user_ns,
	.group_info		= &init_groups,
};

init_cred 구조체는 초기 root 권한의 자격증명을 가지고 있는 것을 확인할 수 있다. 만약 daemon이 NULL이라면 kmem_cache_alloc() 함수를 통해 할당받은 new에 old 값을 복사하고, new를 리턴한다.

prepare_kernel_cred() 함수의 전체 로직을 한번 정리해보자.

  1. kmem_cache_alloc()에 의해 new 변수에 객체를 할당한다
  1. daemon 값이 NULL이 아니면 get_task_cred() 함수를 호출하여 전달된 프로세스의 자격증명(권한으로 보면될듯)을 old 변수에 저장한다
  1. daemon이 NULL이면 init_cred의 자격증명을 old 변수에 저장한다. init_cred 자격증명은 root이다
  1. validate_creds() 함수를 호출하여 전달된 자격증명(old)의 유효성을 검사한다
  1. atomic_set()을 통해 new→usage 필드에 1을 세팅한다
  1. set_cred_subscriber()을 통해 cred→subscribers 필드에 0을 세팅한다
  1. get_uid(), get_user_ns(), get_group_info() 함수를 통해 현재 new에 복사된 자격증명의 uid, user namespace, group info를 조회한다
  1. security_prepare_creds() 함수를 이용하여 현재 프로세스의 자격증명을 갱신한다.
  1. put_cred()함수를 이용하여 현재 프로세스가 이전에 참조한 자격증명을 해제한다
  1. validate_creds() 함수를 이용하여 새롭게 갱신된 자격증명(new)의 유효성을 검사한다.

결론으로는, 우리가 daemon 을 NULL로 만들수 있으면, root 권한을 가지는 cred 구조체를 얻을수 있고 이게 바로 우리가 이용해야하는 root 권한의 새로운 자격증명이다. 헷갈리면 안되는게 prepare_kenel_cred() 함수를 이용해서 새로운 자격증명(root 권한) 값을 얻는거지, 아직 변경된건 아니다. 생성된 cred 구조체 자격증명을 이용해서 실제 권한상승은 commit_creds()에서 일어난다.

이제 commit_creds() 함수를 알아보자. 이 함수는 현재 프로세스의 신원을 다른걸로 변경하는 함수이다. 여기서 LPE가 일어난다.

3. Commit_creds() 함수


이 함수는 프로세스의 신원을 번경시키는 함수이다. prepare_kenel_cred() 함수를 통해 root 권한의 자격증명을 얻었으면, commit_creds() 함수에서 이를 이용해서 실제 권한상승 즉 신원을 바꿀수 있다. 함수 원형은 다음과 같다

/**
 * commit_creds - Install new credentials upon the current task
 * @new: The credentials to be assigned
 *
 * Install a new set of credentials to the current task, using RCU to replace
 * the old set.  Both the objective and the subjective credentials pointers are
 * updated.  This function may not be called if the subjective credentials are
 * in an overridden state.
 *
 * This function eats the caller's reference to the new credentials.
 *
 * Always returns 0 thus allowing this function to be tail-called at the end
 * of, say, sys_setgid().
 */
int commit_creds(struct cred *new)
{
	struct task_struct *task = current;
	const struct cred *old = task->real_cred;

	kdebug("commit_creds(%p{%d,%d})", new,
	       atomic_read(&new->usage),
	       read_cred_subscribers(new));

	BUG_ON(task->cred != old);
#ifdef CONFIG_DEBUG_CREDENTIALS
	BUG_ON(read_cred_subscribers(old) < 2);
	validate_creds(old);
	validate_creds(new);
#endif
	BUG_ON(atomic_read(&new->usage) < 1);

	get_cred(new); /* we will require a ref for the subj creds too */

	/* dumpability changes */
	if (!uid_eq(old->euid, new->euid) ||
	    !gid_eq(old->egid, new->egid) ||
	    !uid_eq(old->fsuid, new->fsuid) ||
	    !gid_eq(old->fsgid, new->fsgid) ||
	    !cred_cap_issubset(old, new)) {
		if (task->mm)
			set_dumpable(task->mm, suid_dumpable);
		task->pdeath_signal = 0;
		/*
		 * If a task drops privileges and becomes nondumpable,
		 * the dumpability change must become visible before
		 * the credential change; otherwise, a __ptrace_may_access()
		 * racing with this change may be able to attach to a task it
		 * shouldn't be able to attach to (as if the task had dropped
		 * privileges without becoming nondumpable).
		 * Pairs with a read barrier in __ptrace_may_access().
		 */
		smp_wmb();
	}

	/* alter the thread keyring */
	if (!uid_eq(new->fsuid, old->fsuid))
		key_fsuid_changed(new);
	if (!gid_eq(new->fsgid, old->fsgid))
		key_fsgid_changed(new);

	/* do it
	 * RLIMIT_NPROC limits on user->processes have already been checked
	 * in set_user().
	 */
	alter_cred_subscribers(new, 2);
	if (new->user != old->user)
		atomic_inc(&new->user->processes);
	rcu_assign_pointer(task->real_cred, new);
	rcu_assign_pointer(task->cred, new);
	if (new->user != old->user)
		atomic_dec(&old->user->processes);
	alter_cred_subscribers(old, -2);

	/* send notifications */
	if (!uid_eq(new->uid,   old->uid)  ||
	    !uid_eq(new->euid,  old->euid) ||
	    !uid_eq(new->suid,  old->suid) ||
	    !uid_eq(new->fsuid, old->fsuid))
		proc_id_connector(task, PROC_EVENT_UID);

	if (!gid_eq(new->gid,   old->gid)  ||
	    !gid_eq(new->egid,  old->egid) ||
	    !gid_eq(new->sgid,  old->sgid) ||
	    !gid_eq(new->fsgid, old->fsgid))
		proc_id_connector(task, PROC_EVENT_GID);

	/* release the old obj and subj refs both */
	put_cred(old);
	put_cred(old);
	return 0;
}
EXPORT_SYMBOL(commit_creds);

commit_creds() 함수의 주요 흐름은 다음과 같다.

  1. current 변수에 담겨있는 현재 프로세스의 정보를 task_struct 구조체 변수인 task에 저장한다
  1. task 구조체를 이용해서 현재 프로세스가 사용중인 자격증명 정보를 old 변수에 저장한다.
  1. BUG_ON() 함수를 이용해서
    • task→cred 와 old 가 같은지 확인한다
    • new→usage에 저장된 값이 1보다 작은지 확인한다
  1. get_gred() 함수를 이용하여 new 변수에 저장된 자격증명에 참조되있는 정보를 가져온다
  1. uid_eq(), gid_eq() 함수를 이용하여 euid, egid 정보를 new, old 변수들에 담겨있는 값과 비교를 한다
  1. cred_cap_issubset() 함수를 이용하여 두 자격증명(old,new)이 동일한 사용자 namespace에 있는지 확인한다
  1. 다시 uid_eq(), gid_eq() 함수를 이용하여 다음의 값을 확인한다
    • new→fsuid, old→fsuid
    • new→fsgid, old→fsgid

    두 값이 다를 경우 key_fsuid_chaned(), key_fsgid_changed() 함수를 이용하여 현재 프로세스의 fsuid, fsgid,값으로 갱신한다

  1. alter_cred_subscribers() 함수를 이용하여 new 구조체에서subscribers 변수에 2를 더한다
  1. rcu_assign_pointer() 함수를 이용하여 현재 프로세스의 task→real_cred, task→cred 영역에 새로운 자격증명을 등록한다.
  1. alter_cred_subscribers() 함수를 이용하여 old 구조체에서subscribers 변수에 -2를 더한다
  1. put_cred() 함수를 이용하여 이전에 사용된 자격증명을 모두 해제한다.

결론적으로 prepare_kernel_cred() 함수로 root 권한의 자격증명을 얻고, 이를 인자로 하여 commit_creds() 함수를 호출하면 root로의 권한상승을 일으킬수 있다

commit_creds(prepare_kernel_cred(NULL));

4. LPE TEST with Linux Kernel Module


위에서 공부한 이론을 바탕으로 직접 LPE 테스트를 해보자. 소스코드는 ioctl 설명에서 사용한 코드를 이용할것이다.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/cred.h>
   
#include "lpe.h"
MODULE_LICENSE("Dual BSD/GPL");
   
#define DRIVER_NAME "chardev"
       
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM  = 1;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
  
static int     chardev_open(struct inode *, struct file *);
static int     chardev_release(struct inode *, struct file *);
static ssize_t chardev_read(struct file *, char *, size_t, loff_t *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);
static long chardev_ioctl(struct file *, unsigned int, unsigned long);
  
struct file_operations s_chardev_fops = {
    .open    = chardev_open,
    .release = chardev_release,
    .read    = chardev_read,
    .write   = chardev_write,
    .unlocked_ioctl = chardev_ioctl,
};
  
static int chardev_init(void)
{
    int alloc_ret = 0;
    int cdev_err = 0;
    int minor = 0;
    dev_t dev;
   
    printk("The chardev_init() function has been called.");
       
    alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
    if (alloc_ret != 0) {
        printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
        return -1;
    }
    //Get the major number value in dev.
    chardev_major = MAJOR(dev);
    dev = MKDEV(chardev_major, MINOR_BASE);
   
    //initialize a cdev structure
    cdev_init(&chardev_cdev, &s_chardev_fops);
    chardev_cdev.owner = THIS_MODULE;
   
    //add a char device to the system
    cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM);
    if (cdev_err != 0) {
        printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
   
    chardev_class = class_create(THIS_MODULE, "chardev");
    if (IS_ERR(chardev_class)) {
        printk(KERN_ERR  "class_create\n");
        cdev_del(&chardev_cdev);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
   
    device_create(chardev_class, NULL, MKDEV(chardev_major, minor), NULL, "chardev%d", minor);
    return 0;
}
   
static void chardev_exit(void)
{
    int minor = 0;
    dev_t dev = MKDEV(chardev_major, MINOR_BASE);
       
    printk("The chardev_exit() function has been called.");
  
    device_destroy(chardev_class, MKDEV(chardev_major, minor));
   
    class_destroy(chardev_class);
    cdev_del(&chardev_cdev);
    unregister_chrdev_region(dev, MINOR_NUM);
}
  
static int chardev_open(struct inode *inode, struct file *file)
{
    printk("The chardev_open() function has been called.");
    return 0;
}
   
static int chardev_release(struct inode *inode, struct file *file)
{
    printk("The chardev_close() function has been called.");
    return 0;
}
   
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_write() function has been called."); 
    return count;
}
   
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_read() function has been called.");
    return count;
}
   
static struct ioctl_info info;
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    printk("The chardev_ioctl() function has been called.");
   
    switch (cmd) {
        case SET_DATA:
            printk("SET_DATA\n");
            if (copy_from_user(&info, (void __user *)arg, sizeof(info))) {
                return -EFAULT;
            }
        printk("info.size : %ld, info.buf : %s",info.size, info.buf);
            break;
        case GET_DATA:
            printk("GET_DATA\n");
            if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
                return -EFAULT;
            }
            break;
        case GIVE_ME_ROOT:
            printk("GIVE_ME_ROOT\n");
            commit_creds(prepare_kernel_cred(NULL));
            return 0;
 
        default:
            printk(KERN_WARNING "unsupported command %d\n", cmd);
   
        return -EFAULT;
    }
    return 0;
}
  
module_init(chardev_init);
module_exit(chardev_exit);

ioctl 매크로에 GIVE_ME_ROOT: 를 등록하고, 해당 분기시 commit_creds(prepared_kernel_cred(NULL)) 함수가 호출된다.

lpe.h

  1 
  2 #include <linux/ioctl.h>
  3 
  4 struct ioctl_info{
  5        unsigned long size;
  6        char buf[128];
  7 };
  8 
  9 #define             IOCTL_MAGIC         'G'
 10 #define             SET_DATA            _IOW(IOCTL_MAGIC, 2 ,struct ioctl_info)
 11 #define             GET_DATA            _IOR(IOCTL_MAGIC, 3 ,struct ioctl_info)
 12 #define             GIVE_ME_ROOT        _IO(IOCTL_MAGIC, 0)

GIVE_ME_ROOT 매크로는 _IO(IOCTLMAGIC, 0) 으로 설정한다. 따라서 GIVE_ME_ROOT 매크로 이용시 3번째 인자가 없기 때문에 chardev_ioctl() 함수의 arg에는 인자를 넘길 필요없다.

최종적으로 사용자가 ioctl() 함수에 GIVE_ME_ROOT 매크로를 넣어주면, 권한 상승을 일으킬수 있다. 테스트 코드는 다음과 같다

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
 
#include "lpe.h"
 
void main()
{
    int fd, ret;
 
    fd = open("/dev/chardev0", O_NOCTTY);
    if (fd < 0) {
        printf("Can't open device file\n");
        exit(1);
    }
 
    ret = ioctl(fd, GIVE_ME_ROOT);
    if (ret < 0) {
        printf("ioctl failed: %d\n", ret);
        exit(1);
    }
    close(fd);
 
    execl("/bin/sh", "sh", NULL);
}

ioctl(fd,GIVE_ME_ROOT)를 호출하고 execl을 이용해 쉘을 실행시킨다. 현재 id값은 유저 권한이기때문에 정상적으로는 user 권한의 쉘이 떨어져야 하지만, 권한상승이 일어나 root 권한의 쉘이 떨어질 것이다.

Makefile

  1 obj-m += chardev.o
  2 
  3 all:
  4     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
  5 clean:
  6     make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

makefile은 동일하다.

╭─wogh8732@ubuntu ~/Desktop/kernel_study/lpe_test 
╰─$ id
uid=1000(wogh8732) gid=1000(wogh8732) groups=1000(wogh8732),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
╭─wogh8732@ubuntu ~/Desktop/kernel_study/lpe_test 
╰─$ ./test 
# id
uid=0(root) gid=0(root) groups=0(root)
#

lpe.ko를 생성하고 insmod로 커널에 등록한다. 그후 생성된 디바이스 파일(chardev0)를 open한뒤, ioctl() 함수를 호출한 결과이다. 처음에는 나의 uid이지만 test를 실행시키면 root로 권한상승이 일어난걸 볼 수 있다.

5. 참조


728x90

'컴퓨터 관련 과목 > 운영체제 & 커널' 카테고리의 다른 글

Linux kernel protection  (0) 2020.12.06
리눅스 커널 디버깅하기  (8) 2020.12.01
ioctl 이란?  (0) 2020.11.18
linux Character Device Drivers 만들기  (4) 2020.11.16
linux kernel module 만들기  (0) 2020.11.16