kernle_struct
name | size |
---|---|
shm_file_data | 0x20 kmalloc-32 |
seq_operations | 0x20 kmalloc-32 |
msg_msg | 0x30 - 0x1000 |
ldt_struct | 0x10 kmalloc-16(slub) kmalloc-32(slab) |
tty_struct | 0x2e0 kmalloc-1024 |
subprocess_info | 0x60 kmalloc-128 |
cred | 0xa8 kmalloc-192 |
file | 0xe8 kmalloc-256 |
timerfd_ctx | kmalloc-256 |
pipe_buffer | kmalloc-1024 |
packet_socket | kmalloc-2048 |
sk_buff | kmalloc-2048 |
1.shm_file_data
struct shm_file_data {
int id;
struct ipc_namespace *ns;
struct file *file;
const struct vm_operations_struct *vm_ops;
};
size:
0x20 kmalloc-32
利用:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmid = shmget(IPC_PRIVATE, 1023, IPC_CREAT | 0600);
char *shmaddr = (char *)shmat( shmid, NULL, 0); // 会创建0x20的chunk
// 通过shmat系统调用会走到do_shmat()函数
long do_shmat(int shmid, char __user *shmaddr, int shmflg,
ulong *raddr, unsigned long shmlba)
{
...
sfd = kzalloc(sizeof(*sfd), GFP_KERNEL); // 创建 0x20 的chunk
...
}
该结构体的 ns 和 vm_ops 指针均可泄露内核基址
该结构体的 file 指针指向堆区,可以泄露堆地址
2.seq_operation
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);
};
size:
0x20 kmalloc-32
利用:
// 通过打开 /proc/self/stat 文件来创建一个 seq_operations 结构体
int fd = open("/proc/self/stat", O_RDONLY);
// 在打开 /proc/self/stat 文件内核会执行 stat_open -> single_open_size -> single_open
int single_open(struct file *file, int (*show)(struct seq_file *, void *),
void *data)
{
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT); // 创建0x20大小的chunk
int res = -ENOMEM;
if (op) {
op->start = single_start;
op->next = single_next;
op->stop = single_stop;
op->show = show;
res = seq_open(file, op);
if (!res)
((struct seq_file *)file->private_data)->private = data;
else
kfree(op);
}
return res;
}
EXPORT_SYMBOL(single_open);
该结构体的四个指针均可泄露出内核基址
可以通过覆盖start指针,然后当我们调用 read 来读取该打开的stat文件,会调用start指针,控制 rip
3.msg_msg
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 */
};
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
size:
0x31-0x1000 kmalloc-64以上
利用:
// 主要依靠 msgsnd() 和 msgrcv()
int msgget (key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
- msqid 消息队列标识符
- msgp 发送给队列的消息,是一个结构体,第一个字段是long类型
- msgsz 消息的大小
- msgflg 标志位
- msgtyp 0表示接受第一个消息,大于0接受类型等于msgtyp的第一个消息,小于0接受类型等于或小于msgtyp绝对值的第一个消息
// 首先用msgget分配一些 msqid 然后msgsnd用来分配chunk
// msgsnd 会调用到 do_msgsnd -> load_msg -> alloc_msg 最后通过alloc_msg来分配chunk
static long do_msgsnd(int msqid, long mtype, void __user *mtext,
size_t msgsz, int msgflg)
{
// ...
msg = load_msg(mtext, msgsz); // 分配msg对象
if (IS_ERR(msg))
return PTR_ERR(msg);
msg->m_type = mtype;
msg->m_ts = msgsz;
// ...
}
struct msg_msg *load_msg(const void __user *src, size_t len)
{
struct msg_msg *msg;
struct msg_msgseg *seg;
int err = -EFAULT;
size_t alen;
msg = alloc_msg(len); // 分配msg对象
if (msg == NULL)
return ERR_PTR(-ENOMEM);
alen = min(len, DATALEN_MSG);
if (copy_from_user(msg + 1, src, alen)) // 把数据从用户态拷贝到刚刚分配的chunk中
goto out_err;
for (seg = msg->next; seg != NULL; seg = seg->next) { // 根据msg结构体的next找到分配的msg_msgseg结构体,然后将用户态数据拷贝过去
// 这里可以利用userfault卡住,然后篡改next指针来实现任意地址写
len -= alen;
src = (char __user *)src + alen;
alen = min(len, DATALEN_SEG);
if (copy_from_user(seg + 1, src, alen))
goto out_err;
}
err = security_msg_msg_alloc(msg);
if (err)
goto out_err;
return msg;
out_err:
free_msg(msg);
return ERR_PTR(err);
}
static struct msg_msg *alloc_msg(size_t len)
{
struct msg_msg *msg;
struct msg_msgseg **pseg;
size_t alen;
alen = min(len, DATALEN_MSG);
msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); // 先分配一个msg_msg结构体
if (msg == NULL)
return NULL;
msg->next = NULL;
msg->security = NULL;
len -= alen;
pseg = &msg->next;
while (len > 0) {
struct msg_msgseg *seg;
cond_resched();
alen = min(len, DATALEN_SEG);
seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); // 然后根据剩下的大小分配 msg_msgseg 结构体
if (seg == NULL)
goto out_err;
*pseg = seg;
seg->next = NULL;
pseg = &seg->next;
len -= alen;
}
return msg;
out_err:
free_msg(msg);
return NULL;
}
// msgrcv 会走到 do_msgrcv
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
// 这里 msg_handler 指向 do_msg_fill() 函数
// ....
msg = find_msg(msq, &msgtyp, mode); // 通过 find_msg 来定位消息
if (!IS_ERR(msg)) {
/*
* Found a suitable message.
* Unlink it from the queue.
*/
if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
msg = ERR_PTR(-E2BIG);
goto out_unlock0;
}
/*
* If we are copying, then do not unlink message and do
* not update queue parameters.
*/
if (msgflg & MSG_COPY) { // 如果设置了 msg_copy 标志就跳到 out_unlock0 不进行下面的unlink
msg = copy_msg(msg, copy);
goto out_unlock0;
}
list_del(&msg->m_list);
// ....
bufsz = msg_handler(buf, msg, bufsz); // 调用 do_msg_fill() 将数据拷贝回用户态
free_msg(msg); // 释放消息
return bufsz;
}
static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)
{
struct msgbuf __user *msgp = dest;
size_t msgsz;
if (put_user(msg->m_type, &msgp->mtype))
return -EFAULT;
msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
if (store_msg(msgp->mtext, msg, msgsz)) // 将msg拷贝回用户态
return -EFAULT;
return msgsz;
}
int store_msg(void __user *dest, struct msg_msg *msg, size_t len)
{
size_t alen;
struct msg_msgseg *seg;
alen = min(len, DATALEN_MSG);
if (copy_to_user(dest, msg + 1, alen)) // 先将msg结构体的内容拷贝回用户态
return -1;
for (seg = msg->next; seg != NULL; seg = seg->next) {
len -= alen;
dest = (char __user *)dest + alen;
alen = min(len, DATALEN_SEG);
if (copy_to_user(dest, seg + 1, alen)) // 然后将msg结构体的next指针指向的msg_msgseg结构体数据拷贝回用户态
return -1;
}
return 0;
}
next指向前一个消息,可以泄露heap
可以通过篡改msg_msg结构体的m_ts大小,然后在msgrcv的时候实现越界读取
可以通过篡改msg_msg结构体的next指针,然后再msgrcv实现任意地址读取
通过在msgsnd的时候userfault卡住,然后篡改next指针来实现任意地址写
4.ldt_struct
struct ldt_struct {
struct desc_struct *entries;
unsigned int nr_entries;
int slot;
};
size:
0x10 kmalloc-16(slub) kmalloc-32(slab)
利用:
// 通过modify_ldt 系统调用,然后根据第一个参数来选择要执行的函数
SYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr ,
unsigned long , bytecount)
{
int ret = -ENOSYS;
switch (func) {
case 0:
ret = read_ldt(ptr, bytecount); // 第一个参数是0执行read_ldt
break;
case 1:
ret = write_ldt(ptr, bytecount, 1); // 第一个参数是1执行write_ldt
break;
case 2:
ret = read_default_ldt(ptr, bytecount);
break;
case 0x11:
ret = write_ldt(ptr, bytecount, 0);
break;
}
return (unsigned int)ret;
}
write_ldt() 函数会调用 alloc_ldt_struct() 函数来分配 ldt_struct 结构体
static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode)
{
// 将用户态的user_desc结构体对象数据拷贝过来,然后经过一系列check
// ......
new_ldt = alloc_ldt_struct(new_nr_entries); // 通过 alloc_ldt_struct 分配ldt_strict 结构体chunk
if (!new_ldt)
goto out_unlock;
if (old_ldt)
memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE); // 这里可以用竞争的方法来想entries里写入数据
// ....
}
static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries)
{
// .....
new_ldt = kmalloc(sizeof(struct ldt_struct), GFP_KERNEL_ACCOUNT); // 分配chunk
if (!new_ldt)
return NULL;
// ....
}
read_ldt() 函数,控制ldt_struct 的 entries 指针来实现任意地址读取
static int read_ldt(void __user *ptr, unsigned long bytecount)
{
// ....
if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) { // 将entry里的数据拷贝给用户态
retval = -EFAULT;
goto out_unlock;
}
// ....
}
// copy_to_user 对于非法地址不会造成kernel panic,会返回一个非0的错误码
// 在遇到hardened usercopy保护的时候可以利用fork来完成任意地址读取
5.tty_struct
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;
size:
0x2e0 kmalloc-1024
利用:
// 通过打开 /dev/ptmx 来分配该结构体
open("/dev/ptmx", O_RDWR | O_NOCTTY);
// 要能正常打开 dev/ptmx 文件需要在init脚本里加入下面几句
// mkdir -p /dev/pts
// mount -vt devpts -o gid=4,mode=620 none /dev/pts
// 打开 /dev/ptmx 会调用到 ptmx_open() 函数 -> tty_init_dev -> alloc_tty_struct 分配tty_struct
ops指向 ptm_unix98_ops可泄露内核基址
dev和driver可泄露堆地址
通过重写ops函数表可以控制RIP
tty_struct 第一个字段的魔数是0x5401,可以通过该数字锁定结构体
// tty_struct的ops指针对应的 tty_operations结构体
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
unsigned int (*write_room)(struct tty_struct *tty);
unsigned int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;
subprocess_info
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;
size:
0x60 kmalloc-128
利用:
work.func可以泄露内核基地址
重写cleanup可以劫持RIP,不过要通过竞争的方法
// 通过socket(22, AF_INET, 0)可分配一个该堆块
// 后面又马上会释放掉
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;
size:
0xa8 kmalloc-192
利用:
通过创建进程来分配该结构体
可以通过session_keyring字段泄露堆地址
将uid和gid覆盖为0即可提权
file
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
size:
0xe8 kmalloc-256
利用
通过shmget创建共享内存来分配该结构体
f_op指针可泄露内核基址
timerfd_ctx
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 hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
u8 state;
u8 is_rel;
u8 is_soft;
};
size:
kmalloc-256
利用:
通过timerfd_create()创建
通过tmr.function
所指向的timerfd_tmrproc
来泄露内核基址
通过tmr.base
泄露堆地址
pipe_buffer
struct pipe_buffer {
struct page *page; // 读写pipe时, 实际上是读写page地址
unsigned int offset, len;
const struct pipe_buf_operations *ops; // <-------- 函数表
unsigned int flags;
unsigned long private;
};
struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *); // 确保 pipe buffer 中的数据有效,有效则返回0,无效则返回负值错误码。
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);// <-------- 释放 pipe buffer
bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};
size:
0x370 kmalloc-1024
利用:
// pipe() -> do_pipe2() -> __do_pipe_flags() -> create_pipe_files() -> get_pipe_inode() -> alloc_pipe_info() —— 分配大小为0x370(默认16个page,16*0x28=0x370)
struct pipe_inode_info *alloc_pipe_info(void)
{
// .....
pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
GFP_KERNEL_ACCOUNT);
// .....
}
int fd[2];
pipe(fd);
write(fd[1], "pwn", 3);
可通过ops泄露内核基址,ops指向pipe_buf_operations函数表
可通过劫持pipe_buffer->ops->release来劫持RIP,close pipe的时候,触发链
- pipe_release() -> put_pipe_info() -> free_pipe_info -> pipe_buf_release() 调用
pipe_buffer->ops->release
函数