原文链接:任意データ書き込み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(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