【Kernel】一些trick和struct记录(持续更新)

pt_regs

用于保存用户态–>内核态切换时的用户态信息,在内核态返回时会从pt_regs恢复数据。因而若我们能控制rsp到达此位置,即可以使用用户态寄存器值辅助实现ROP

img

list_head结构体

简单的内核双链表结构,在内核中经常作为实现双链表结构的基础,定义如下

//	https://elixir.bootlin.com/linux/latest/source/include/linux/types.h#L184

struct list_head {
    struct list_head *next, *prev;
};

setxattr系统调用

从a3✌的d3kheap中学到的东西,a3✌也太超人了吧!!!原文链接如下:

【CTF.0x06】D^ 3CTF2022 d3kheap 出题手记 - arttnba3’s blog

setxattr 是一个十分独特的系统调用族,抛开其本身的功能,在 kernel 的利用当中他可以为我们提供近乎任意大小的内核空间 object 分配

观察setxattr源码,发现如下调用链:

SYS_setxattr()
    path_setxattr()
        setxattr()

setxattr()函数中有如下逻辑:

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
     size_t size, int flags)
{
    //...
        kvalue = kvmalloc(size, GFP_KERNEL);
        if (!kvalue)
            return -ENOMEM;
        if (copy_from_user(kvalue, value, size)) {

    //,..

    kvfree(kvalue);

    return error;
}

这里的 value 和 size 都是由我们来指定的,即我们可以分配任意大小的 object 并向其中写入内容,完成写入之后该 object 又会通过 kvfree 被释放掉,因此我们便可以通过 setxattr 多次修改 victim 的内容

msg_queue消息队列

同样来自a3✌的blog, a3✌简直是我Kernel的启蒙老师😭😭😭

【CTF.0x06】D^ 3CTF2022 d3kheap 出题手记 - arttnba3’s blog

The syscall about system V message queue

  • msgget:create a msg queue
  • msgsnd:send msg to a msg queue
  • msgrcv:recive msg from a msg queue

System will create a structure named msg_queue when we create a message queue. Here is the definition of this structure.

/* one msq_queue structure for each present queue on the system */
struct msg_queue {
    struct kern_ipc_perm q_perm;
    time64_t q_stime;		/* last msgsnd time */
    time64_t q_rtime;		/* last msgrcv time */
    time64_t q_ctime;		/* last change time */
    unsigned long q_cbytes;		/* current number of bytes on queue */
    unsigned long q_qnum;		/* number of messages in queue */
    unsigned long q_qbytes;		/* max number of bytes on queue */
    struct pid *q_lspid;		/* pid of last msgsnd */
    struct pid *q_lrpid;		/* last receive pid */

    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
} __randomize_layout;

When we try to call msgsnd to send a message to a specified message queue, such a srturcture is created in kernel space:

/* one msg_msg structure for each message */
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 */
};

实际结构如下:

image.png

进程描述符(process descriptor)

在内核中使用结构体 task_struct 表示一个进程,该结构体定义于内核源码include/linux/sched.h中,代码比较长就不在这里贴出了.一个进程描述符的结构应当如下图所示:

image.png

注意到task_struct的源码中有如下代码:

/* Process credentials: */

/* Tracer's credentials at attach: */
const struct cred __rcu        *ptracer_cred;

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu        *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu        *cred;

Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred 结构体进行标识,对于一个进程而言应当有三个 cred:

  • ptracer_cred:使用ptrace系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
  • real_cred:即客体凭证objective cred),通常是一个进程最初启动时所具有的权限
  • cred:即主体凭证subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证

一般情况下,主体凭证与客体凭证的值是相同的

例:当进程 A 向进程 B 发送消息时,A为主体,B为客体

进程权限凭证:cred结构体

对于一个进程,在内核当中使用一个结构体cred管理其权限,该结构体定义于内核源码include/linux/cred.h中,如下:

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    *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;

我们主要关注cred结构体中管理权限的变量

用户ID & 组ID

一个cred结构体中记载了一个进程四种不同的用户ID

  • 真实用户ID(real UID):标识一个进程启动时的用户ID
  • 保存用户ID(saved UID):标识一个进程最初的有效用户ID
  • 有效用户ID(effective UID):标识一个进程正在运行时所属的用户ID,一个进程在运行途中是可以改变自己所属用户的,因而权限机制也是通过有效用户ID进行认证的,内核通过 euid 来进行特权判断;为了防止用户一直使用高权限,当任务完成之后,euid 会与 suid 进行交换,恢复进程的有效权限
  • 文件系统用户ID(UID for VFS ops):标识一个进程创建文件时进行标识的用户ID

在通常情况下这几个ID应当都是相同的

用户组ID同样分为四个:真实组ID保存组ID有效组ID文件系统组ID,与用户ID是类似的,这里便不再赘叙

页->区->节点三级结构

页page

Linux kernel 中使用 page 结构体来表示一个物理页框,每个物理页框都有着一个对应的 page 结构体

image.png

区zone

在 Linux 下将一个节点内不同用途的内存区域划分为不同的区(zone),对应结构体 struct zone

自己画的图.png

节点node

zone 再向上一层便是节点——Linux 将_内存控制器(memory controller)_作为节点划分的依据,对于 UMA 架构而言只有一个节点,而对于 NUMA 架构而言通常有多个节点,对于同一个内存控制器下的 CPU 而言其对应的节点称之为_本地内存_,不同处理器之间通过总线进行进一步的连接。如下图所示,一个 MC 对应一个节点:

image.png

tty struct

/dev/ptmx 是一个特殊文件,用于在 Unix-like 系统中创建伪终端(pseudo-terminal)。伪终端是一种虚拟的终端设备,通常用于在程序之间提供类似于终端的交互界面,比如通过 SSH 远程登录、终端仿真器等。

当使用open()打开该设备后,会在内核空间分配一个tty_struct结构体,如图

tty_struct

对我们来说,最关键的便是其中的const struct tty_operations *ops;,由结构体的名称tty_operations我们也能猜测出其为表状结构。该结构体定义在linux/tty_driver.h,如下

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);
    ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t count);
    int  (*put_char)(struct tty_struct *tty, u8 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, const 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 *m, void *driver);
} __randomize_layout;

拥有丰富的函数指针,其中open, read, write等等函数也放置其中。常见利用手法便是劫持tty_operation的函数指针,这样我们在对/dev/ptmx设备进行读写操作时,便可调用劫持后的指针地址。


下面这些留个坑,有空补上

pipe_buffer

seq operation