Java openrasp记录-02

主要分析以下四个部分:

1.openrasp agent

这里主要进行插桩的定义,其pom.xml中定义了能够当类重新load时重定义以及重新转换

 

这里定义了两种插桩方式对应之前安装时的独立web的jar的attach或者修改启动脚本添加rasp的jar的方式

 

其中init操作则需要将rasp.jar添加到Bootstrap路径中,因为后面修改字节码时将涉及到bootstraploader加载的一些类,正常情况下由rasp位于System class path根据类加载机制是拦截不到的bootstrapclassloader的类加载路径下的class,加入到Bootstrapclassloader的搜索路径下以后,才能拦截到

 

 接着调用Moduleloader.load,通过选择mode(premain或者agentmain),action(install或者uninstall),该类主要进行加载和初始化引擎模块rasp-engine.jar

 

load方法将会根据选择的action来new一个moduloader,传入模式和inst

 

moduleLoader中将使用rasp引擎jar文件new一个ModuleContainer容器(static代码块主要完成获取rasp.jar路径以及设置moduleclassloader),然后启动该引擎容器,传入插桩方式mode和插桩实例inst

 

启动引擎函数:

根据加载的agent\java\engine下面的主类来启动rasp引擎

 也就是rasp-engine.jar的manifest.mf里面所定义的EngineBoot类的start方法,模块名为rasp-engine,采用低版本的1.6.0_45打包可以兼容高版本

 

2.openrasp engine

主要的一些rasp具体的操作逻辑,包括hook操作

 根据第一部分初始化的最后一个阶段调用rasp引擎模块的start方法,对应Engineboot类,所以直接定位到该类:

public class EngineBoot implements Module { //该类是实现Moudle接口的,因此可以调用start方法private CustomClassTransformer transformer; //定义类转换器@Overridepublic void start(String mode, Instrumentation inst) throws Exception {System.out.println("\n\n" + //rasp打印标志"   ____                   ____  ___   _____ ____ \n" +"  / __ \\____  ___  ____  / __ \\/   | / ___// __ \\\n" +" / / / / __ \\/ _ \\/ __ \\/ /_/ / /| | \\__ \\/ /_/ /\n" +"/ /_/ / /_/ /  __/ / / / _, _/ ___ |___/ / ____/ \n" +"\\____/ .___/\\___/_/ /_/_/ |_/_/  |_/____/_/      \n" +"    /_/                                          \n\n");try {Loader.load(); //加载v8引擎,用于解释js} catch (Exception e) {System.out.println("[OpenRASP] Failed to load native library, please refer to https://rasp.baidu.com/doc/install/software.html#faq-v8-load for possible solutions.");e.printStackTrace();return;}if (!loadConfig()) { //进行rasp引擎的初始化配置return;}//缓存rasp的build信息Agent.readVersion();BuildRASPModel.initRaspInfo(Agent.projectVersion, Agent.buildTime, Agent.gitCommit);// 初始化js插件系统if (!JS.Initialize()) {return;}CheckerManager.init(); //初始化所有类型的checker,包括js插件检测,java本地检测,服务器基线检测initTransformer(inst);if (CloudUtils.checkCloudControlEnter()) {CrashReporter.install(Config.getConfig().getCloudAddress() + "/v1/agent/crash/report",Config.getConfig().getCloudAppId(), Config.getConfig().getCloudAppSecret(),CloudCacheModel.getInstance().getRaspId());}deleteTmpDir();String message = "[OpenRASP] Engine Initialized [" + Agent.projectVersion + " (build: GitCommit="+ Agent.gitCommit + " date=" + Agent.buildTime + ")]";System.out.println(message);Logger.getLogger(EngineBoot.class.getName()).info(message);}@Overridepublic void release(String mode) {CloudManager.stop();CpuMonitorManager.release();if (transformer != null) {transformer.release();}JS.Dispose();CheckerManager.release();String message = "[OpenRASP] Engine Released [" + Agent.projectVersion + " (build: GitCommit="+ Agent.gitCommit + " date=" + Agent.buildTime + ")]";System.out.println(message);}private void deleteTmpDir() {try {File file = new File(Config.baseDirectory + File.separator + "jar_tmp");if (file.exists()) {FileUtils.deleteDirectory(file);}} catch (Throwable t) {Logger.getLogger(EngineBoot.class.getName()).warn("failed to delete jar_tmp directory: " + t.getMessage());}}/*** 初始化配置** @return 配置是否成功*/private boolean loadConfig() throws Exception {LogConfig.ConfigFileAppender();  //初始化log4j的logger//单机模式下动态添加获取删除syslogif (!CloudUtils.checkCloudControlEnter()) {LogConfig.syslogManager();} else {System.out.println("[OpenRASP] RASP ID: " + CloudCacheModel.getInstance().getRaspId());}return true;}/*** 初始化类字节码的转换器** @param inst 用于管理字节码转换器*/private void initTransformer(Instrumentation inst) throws UnmodifiableClassException {transformer = new CustomClassTransformer(inst);transformer.retransform();}}
v8的引擎的初始化,调用的为本地java代码的initalize方法public synchronized static boolean Initialize() {try {if (!V8.Initialize()) {throw new Exception("[OpenRASP] Failed to initialize V8 worker threads");}V8.SetLogger(new com.baidu.openrasp.v8.Logger() { //设置v8的logger@Overridepublic void log(String msg) {PLUGIN_LOGGER.info(msg);}});V8.SetStackGetter(new com.baidu.openrasp.v8.StackGetter() { //设置v8获取栈信息的getter方法,这里获得的栈信息,每一条信息包括类名、方法名和行号classname@methodname(linenumber)@Overridepublic byte[] get() {try {ByteArrayOutputStream stack = new ByteArrayOutputStream();JsonStream.serialize(StackTrace.getParamStackTraceArray(), stack);stack.write(0);return stack.getByteArray();} catch (Exception e) {return null;}}});Context.setKeys();if (!CloudUtils.checkCloudControlEnter()) {UpdatePlugin(); //加载js插件到v8引擎中InitFileWatcher(); //启动对js插件的文件监控,从而实现热部署,动态的增删js中的检测规则}return true;} catch (Exception e) {e.printStackTrace();LOGGER.error(e);return false;}}

updatePlugin:

其中涉及到rasp hook功能的开关,关于rasp绕过的一种方式就是通过反射关掉这个引擎

 

接着获取到js插件的目录plugins

 

默认就是official.js,检测各种攻击的逻辑就写在里面,用js写实现热部署,并加载到v8引擎中

InitFileWatcher:

这里利用jnotify对js插件目录进行监控,用的代码是openrasp二次开发过的GitHub - baidu-security/openrasp-jnotify: 为 OpenRASP 专门定制开发的 jnotify 版本,支持 Mac、Win、Linux

public synchronized static void InitFileWatcher() throws Exception {boolean oldValue = HookHandler.enableHook.getAndSet(false); if (watchId != null) { //监听器idFileScanMonitor.removeMonitor(watchId); //移除监听器watchId = null;}watchId = FileScanMonitor.addMonitor(Config.getConfig().getScriptDirectory(), new FileScanListener() {@Overridepublic void onFileCreate(File file) {if (file.getName().endsWith(".js")) {UpdatePlugin();}}@Overridepublic void onFileChange(File file) {if (file.getName().endsWith(".js")) {UpdatePlugin();}}@Overridepublic void onFileDelete(File file) {if (file.getName().endsWith(".js")) {UpdatePlugin();}}});HookHandler.enableHook.set(oldValue);}

addMonitor将传入监听目录和事件回调接口,最后返回监听器id,其中mask定义了创建+删除+修改三种模式,对应回调函数则重写了OnfileCreate、OnfileChange、OnfileDelete三种方法,只要是后缀为js的文件被创建、删除或者修改了则调用UpdatePlugin方法重新读取plugins目录下的检测js逻辑并重新加载到v8引擎中

 

 CheckerManager.init方法:

public class CheckerManager {private static EnumMap<Type, Checker> checkers = new EnumMap<Type, Checker>(Type.class);public synchronized static void init() throws Exception {for (Type type : Type.values()) {checkers.put(type, type.checker); //加载所有类型的检测放入checkers,type.checker就是某种检测对应的类}}public synchronized static void release() {checkers = null;}public static boolean check(Type type, CheckParameter parameter) {return checkers.get(type).check(parameter); //调用检测类进行参数检测}}

包括使用js插件进行检测的,对应的是类V8AttackChecker,就是调用V8引擎加载js进行检测

本地检测的两种攻击:

 

另外一些也是是本地的类检查的,一些服务器安全配置检查,数据库连接以及日志检查

 

接着CheckManager.init结束以后,此时将初始换插桩用的转换器

 自定义classTransformer:

/** Copyright 2017-2020 Baidu Inc.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.baidu.openrasp.transformer;import com.baidu.openrasp.ModuleLoader;
import com.baidu.openrasp.config.Config;
import com.baidu.openrasp.dependency.DependencyFinder;
import com.baidu.openrasp.detector.ServerDetectorManager;
import com.baidu.openrasp.hook.AbstractClassHook;
import com.baidu.openrasp.messaging.ErrorType;
import com.baidu.openrasp.messaging.LogTool;
import com.baidu.openrasp.tool.annotation.AnnotationScanner;
import com.baidu.openrasp.tool.annotation.HookAnnotation;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import org.apache.log4j.Logger;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.ref.SoftReference;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;/*** 自定义类字节码转换器,用于hook类的方法*/
public class CustomClassTransformer implements ClassFileTransformer {public static final Logger LOGGER = Logger.getLogger(CustomClassTransformer.class.getName());private static final String SCAN_ANNOTATION_PACKAGE = "com.baidu.openrasp.hook"; //hook的类所在的包,hook的类都有对应的注解标注private static HashSet<String> jspClassLoaderNames = new HashSet<String>(); //保存要用到的一些类加载器private static ConcurrentSkipListSet<String> necessaryHookType = new ConcurrentSkipListSet<String>(); private static ConcurrentSkipListSet<String> dubboNecessaryHookType = new ConcurrentSkipListSet<String>(); //dubbo要hook的类型public static ConcurrentHashMap<String, SoftReference<ClassLoader>> jspClassLoaderCache = new ConcurrentHashMap<String, SoftReference<ClassLoader>>();private Instrumentation inst;private HashSet<AbstractClassHook> hooks = new HashSet<AbstractClassHook>(); //各种攻击对应的hook类的实例private ServerDetectorManager serverDetector = ServerDetectorManager.getInstance();public static volatile boolean isNecessaryHookComplete = false; //volatile修饰,保证多线程下该共享变量的可见性,值更改后立即刷新到主存,工作线程才能够从内存中取到新的值public static volatile boolean isDubboNecessaryHookComplete = false; //dubbo的hookstatic {jspClassLoaderNames.add("org.apache.jasper.servlet.JasperLoader");  //类加载要用到的一些类加载器jspClassLoaderNames.add("com.caucho.loader.DynamicClassLoader");jspClassLoaderNames.add("com.ibm.ws.jsp.webcontainerext.JSPExtensionClassLoader");jspClassLoaderNames.add("weblogic.servlet.jsp.JspClassLoader");dubboNecessaryHookType.add("dubbo_preRequest");dubboNecessaryHookType.add("dubboRequest");}public CustomClassTransformer(Instrumentation inst) {this.inst = inst;inst.addTransformer(this, true);addAnnotationHook(); //在这要操作所有带hook注解的类了,虽然看注解用上貌似效率慢一点,但是这里用起来感觉还是很方便}public void release() {inst.removeTransformer(this);retransform();}public void retransform() {LinkedList<Class> retransformClasses = new LinkedList<Class>();Class[] loadedClasses = inst.getAllLoadedClasses();for (Class clazz : loadedClasses) {if (isClassMatched(clazz.getName().replace(".", "/"))) {if (inst.isModifiableClass(clazz) && !clazz.getName().startsWith("java.lang.invoke.LambdaForm")) {try {// hook已经加载的类,或者是回滚已经加载的类inst.retransformClasses(clazz);} catch (Throwable t) {LogTool.error(ErrorType.HOOK_ERROR,"failed to retransform class " + clazz.getName() + ": " + t.getMessage(), t);}}}}}private void addHook(AbstractClassHook hook, String className) { //正常情况下将添加所有带注解的hook点if (hook.isNecessary()) { //默认是falsenecessaryHookType.add(hook.getType()); //每种hook类对应一个type,例如读文件、删除文件、xxe、ognl}String[] ignore = Config.getConfig().getIgnoreHooks(); //拿到不hook的类名,支持配置的for (String s : ignore) {if (hook.couldIgnore() && (s.equals("all") || s.equals(hook.getType()))) { //hook点可以忽略LOGGER.info("ignore hook type " + hook.getType() + ", class " + className);return;}}hooks.add(hook);}private void addAnnotationHook() {Set<Class> classesSet = AnnotationScanner.getClassWithAnnotation(SCAN_ANNOTATION_PACKAGE, HookAnnotation.class); //取到所有带HookAnnotaion.class注解的类for (Class clazz : classesSet) { try {Object object = clazz.newInstance(); //实例化每种攻击对应的hook类if (object instanceof AbstractClassHook) {addHook((AbstractClassHook) object, clazz.getName());}} catch (Exception e) {LogTool.error(ErrorType.HOOK_ERROR, "add hook failed: " + e.getMessage(), e);}}}/*** 过滤需要hook的类,进行字节码更改** @see ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])*/@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException {  //transform也就是实际插桩生效的地方,loadclass到jvm中时触发if (loader != null) {DependencyFinder.addJarPath(domain); //因为用到的class可能是某个jar包中的,因此这里根据当前保护域去找到当前load的class的绝对路径,若其存在,则将对应的jar包加到loadedJarPath中}if (loader != null && jspClassLoaderNames.contains(loader.getClass().getName())) { //如果当前的类加载器是jsp相关的类加载器jspClassLoaderCache.put(className.replace("/", "."), new SoftReference<ClassLoader>(loader)); //这里用softReference对jsp相关的classloader进行弱引用封装,SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,后面jvm再根据内存情况(堆上情况)和SoftReference.get来决定要不要回收该对象,弱引用封装的对象通过get拿到对象的强引用再使用对象,这里是为了防止classloader内存泄露}for (final AbstractClassHook hook : hooks) { //对添加到hooks中的所有类别的hook点进行遍历if (hook.isClassMatched(className)) {  //此时要判断要hook的类名CtClass ctClass = null;try {ClassPool classPool = new ClassPool(); //要用到javaassist技术改变字节码了addLoader(classPool, loader);  //初始化class文件的搜索路径ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));if (loader == null) {hook.setLoadedByBootstrapLoader(true);}classfileBuffer = hook.transformClass(ctClass);if (classfileBuffer != null) {checkNecessaryHookType(hook.getType());}} catch (IOException e) {e.printStackTrace();} finally {if (ctClass != null) {ctClass.detach();}}}}serverDetector.detectServer(className, loader, domain);return classfileBuffer;}private void checkNecessaryHookType(String type) {if (!isNecessaryHookComplete && necessaryHookType.contains(type)) {necessaryHookType.remove(type);if (necessaryHookType.isEmpty()) {isNecessaryHookComplete = true;}}if (!isDubboNecessaryHookComplete && dubboNecessaryHookType.contains(type)) {dubboNecessaryHookType.remove(type);if (dubboNecessaryHookType.isEmpty()) {isDubboNecessaryHookComplete = true;}}}public boolean isClassMatched(String className) {for (final AbstractClassHook hook : getHooks()) {if (hook.isClassMatched(className)) {return true;}}return serverDetector.isClassMatched(className);}private void addLoader(ClassPool classPool, ClassLoader loader) {classPool.appendSystemPath(); //添加jvm启动时的一些搜索路径比如扩展类,rt.jar或者classpath下的类classPool.appendClassPath(new ClassClassPath(ModuleLoader.class));if (loader != null) {classPool.appendClassPath(new LoaderClassPath(loader));}}public HashSet<AbstractClassHook> getHooks() {return hooks;}}

hook的相关类

 

 判断是不是某个注解的hook类对应的要进行插桩的class

 

3.openrasp安装时的一些检测代码

其中App.java为安装rasp的主程序

 

根据nodetect选择安装模式:

nodetect模式下attach方法:

找到服务器对应的启动脚本并修改

 

不同系统支持的平台如下所示:

 

 operateServer主要在这个阶段要完成的是:

1.根据不同的操作系统种类使用不同的工厂类,调用工厂类的getInstaller来根据nodetect参数判断目标程序是否是以springboot型的独立jar启动选择GenericInstaller模式安装(此时将定义不需要修改启动shell脚本去插入一下启动rasp的配置项,直接使用attach模式根据提供的pid进行attach)。若nodetect为false,则要探测一些服务器的标志文件去判断目标服务器种类拿到Installer的实例,后面则要根据不同服务器种类去修改相应的服务器的shell启动脚本添加加载rasp的配置项

2.拿到GenericInstaller或者Installer后调用其install方法进行rasp的安装,Installer的install调用中需要去找到服务器的启动脚本添加配置项

4.openrasp的攻击检测插件,检查攻击的源码

之前分析到rasp在初始化js插件时将会把plugins下的js文件加载到v8引擎中,来实现热部署,这部分检测逻辑代码太多啦,这里对于不同语言使用js来实现检测逻辑,从而实现通用检测,我只关心java相关的漏洞检查,除了下面列出的一些CVE,还包括java的一些通用漏洞的检测,这部分单独将进行研究。

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

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

相关文章

大数据技术主要学什么,有哪些课程

大数据技术是指在海量数据的环境下&#xff0c;采集、存储、处理、分析和管理数据的一系列技术与方法。随着互联网、物联网以及各种智能设备的普及&#xff0c;数据量呈爆炸性增长&#xff0c;传统数据处理手段已难以应对&#xff0c;因此大数据技术应运而生&#xff0c;旨在从…

加州大学欧文分校英语中级语法专项课程04:Intermediate Grammar Project学习笔记(完结)

Intermediate Grammar Project Course Certificate Specialization Certificate Specialization Intro Course Intro 本文是学习 Coursera: Intermediate Grammar Project 这门课的学习笔记。 文章目录 Intermediate Grammar ProjectWeek 01: IntroductionCapstone Introducti…

论文笔记:DeepMove: Predicting Human Mobility with Attentional Recurrent Networks

WWW 2018 1 Intro 根据对百万级用户群的研究&#xff0c;93%的人类移动是可预测的。 早期的mobility预测方法大多基于模式的。 首先从轨迹中发现预定义的移动模式(顺序模式、周期模式)然后基于这些提取的模式预测未来位置。最近的发展转向基于模型的方法进行流动性预测。 利用…

力扣:62. 不同路径

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

五一假期后,必读的10篇大模型论文

1.同时预测多个 token&#xff1a;更好更快的大型语言模型 目前&#xff0c;GPT 和 Llama 等大型语言模型&#xff08;LLMs&#xff09;都是通过下一个 token 预测损失来训练的。 在这项工作中&#xff0c;来自 Meta FAIR 的研究团队认为&#xff0c;训练语言模型同时预测多个…

用 Go map 要注意这个细节,避免依赖他!

有的小伙伴没留意过 Go map 输出、遍历顺序&#xff0c;以为它是稳定的有序的&#xff0c;会在业务程序中直接依赖这个结果集顺序&#xff0c;结果栽了个大跟头&#xff0c;吃了线上 BUG。 有的小伙伴知道是无序的&#xff0c;但却不知道为什么,有的却理解错误&#xff1f; 今…

PADS 规则设置-导线不跟随器件-导线允许回路

1、PADS Layout中设置拖动器件时导线不跟着移动 2、PADS Router中设置走线允许回路

【隧道篇 / WAN优化】(7.4) ❀ 01. 启动WAN优化 ❀ FortiGate 防火墙

【简介】几乎所有的人都知道&#xff0c;防火墙自带的硬盘是用来保存日志&#xff0c;以方便在出现问题时能找到原因。但是很少的人知道&#xff0c;防火墙自带的硬盘其实还有另一个功能&#xff0c;那就是用于WAN优化。 防火墙自带的硬盘 在FortiGate防火墙A、B、C、D系列&…

【备战软考(嵌入式系统设计师)】04-嵌入式软件架构

嵌入式操作系统 嵌入式系统有以下特点&#xff1a; 要求编码体积小&#xff0c;能够在有限的存储空间内运行。 面向应用&#xff0c;可以进行裁剪和移植。 用于特定领域&#xff0c;可以支持多任务。 可靠性高&#xff0c;及时响应&#xff0c;无需人工干预独立运行。 实…

软件全套资料整理包获取-软件各阶段支撑文档

软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需求说明书&#xff0c;概要设计说明书&#xff0c…

动手写一个简单的Android 表格控件支持固定列

Android 动手写一个简洁版表格控件 简介 源码已放到 Github Gitee 作为在测绘地理信息行业中穿梭的打工人&#xff0c;遇到各种数据采集需求&#xff0c;既然有数据采集需求&#xff0c;那当然少不了数据展示功能&#xff0c;最常见的如表格方式展示。 当然&#xff0c;类似…

大模型时序预测初步调研20240506

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

MySQL —— 表的基本操作

一、创建 1.语法 create table 表名称( 自定义变量1, 自定义变量2, 自定义变量3&#xff08;最后一个变量末尾不需要加任何标点符号&#xff09; )charset字符集 collate校验集 engine存储引擎; ps&#xff1a;若是不具体给字符集、校验集、储存引擎&#xff0c;则采用配置文件…

『跨端框架』Flutter环境搭建

『跨端框架』Flutter环境搭建 资源网站简介跨平台高性能发展历程跨平台框架的比较成功案例 环境搭建&#xff08;windows&#xff09;基础环境搭建Windows下的安卓环境搭建Mac下的安卓环境配置资源镜像JDKAndroid StudioFlutter SDK问题一问题二问题三修改项目中的Flutter版本 …

厂家自定义 Android Ant编译流程源码分析

0、Ant安装 Windows下安装Ant&#xff1a; ant 官网可下载 http://ant.apache.org ant 环境配置&#xff1a; 解压ant的包到本地目录。 在环境变量中设置ANT_HOME&#xff0c;值为你的安装目录。 把ANT_HOME/bin加到你系统环境的path。 Ubuntu下安装Ant&#xff1a; sudo apt…

visio studio 中.NET Core(.net8.0)框架和.net framewok 框架有什么区别?

更新vs到2022版本后&#xff0c;新建项目时就多出不少选项&#xff0c;这里来个大家分享下.NET Core&#xff08;.net8.0&#xff09;框架和.net framewok的区别 如下图&#xff0c;不带后缀的就是使用.NET Core框架&#xff0c;后续选项是.net8.0。 .net framewok框架选项&am…

从0到1:商场导览小程序开发笔记一

背景 购物中心与商场小程序&#xff1a;旨在提供便捷的购物、导航、活动报名、服务查询等功能&#xff0c;让用户更好地体验购物和享受服务。通过提供便捷的购物、信息查询和互动预约等功能&#xff0c;提升了商场的服务水平和用户体验&#xff0c;帮助商场与消费者建立更紧密…

YOLOv5入门(四)训练自己的目标检测模型

前言 通过前面几篇文章&#xff0c;已经完成数据集制作和环境配置&#xff08;服务器&#xff09;&#xff0c;接下来将继续实践如何开始训练自己数据集~ 往期回顾 YOLOv5入门&#xff08;一&#xff09;利用Labelimg标注自己数据集 YOLOv5入门&#xff08;二&#xff09;处…

Android 14 init进程解析

前言 当bootloader启动后&#xff0c;启动kernel&#xff0c;kernel启动完后&#xff0c;在用户空间启动init进程&#xff0c;再通过init进程&#xff0c;来读取init.rc中的相关配置&#xff0c;从而来启动其他相关进程以及其他操作。 init进程启动主要分为两个阶段&#xff1…

jsPDF + html2canvas + Vue3 + ts项目内,分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF,隐藏导出按钮等多余元素

jsPDF html2canvas Vue3 ts Arco Design项目&#xff0c;分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF&#xff0c;隐藏导出按钮等多余元素… 1.下载所需依赖 pnpm install --save html2canvaspnpm install --save jspdf引入依赖 <script setup lang"…