Javassist dynamic programming for Java Security

Javassist dynamic programming for Java Security

0x00 Preface

Before debugging the CC2 chain, let's fill in the knowledge blind spot and learn about the specific role of javassist. In CC2 chain, javassist and PriorityQueue will be used to construct the utilization chain

0x01 javassist introduction

Java bytecode is stored in class files in binary form, and each class file contains a Java class or interface. Javaassist is a class library used to process Java bytecode.

Javassist is an open source class library for analyzing, editing and creating Java bytecode.

0x02 javassist usage

Here are the main categories:

ClassPool

Classpool: a container of ctclass objects implemented based on a hash table, where the key name is the class name and the value is the ctclass object representing the class (both hashtable and HashMap implement the map interface, and HashMap can receive null values, but hashtable cannot).

Common methods:

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 represents a class. A ctclass (compile time class) object can process a class file. These ctclass objects can be obtained from some methods of classpool.

Common methods:

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: represents the method in the class.

CtConstructor

An instance of ctconstructor represents a constructor. It may represent a static constructor (class initializer).

common method

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

ClassClassPath

This class is used to obtain the search path of class files in java.lang.class through getresourceasstream().

Construction method:

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

Common methods:

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

Code example:

ClassPool pool = ClassPool.getDefault();

Get the classpool object in the default system search path.

If you need to modify the path of class search, you need to use the insertclasspath method to modify it.

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

Insert the path of this class into the search path

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);
    }

}

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
    }
}

0x03 some small ideas

According to my understanding, it is possible to convert classes and bytecodes to each other. So what can we do if we extend the application according to this idea? The first thing I think of may be some free killing of webshell. For example, the most common webshell of JSP uses two classes, runtime and processbuilder, to construct and execute commands. According to the inertia of WAF, these devices must pull these common execution command functions into the blacklist. So what if it can be converted into bytecode? Bytecode will not be killed. If the runtime class is converted into bytecode at this time, embedded in JSP, and then javassist is used to restore the bytecode into a class, WAF can be implemented if several methods of conversion are not killed. Of course, these are just my assumptions, because javassist is not built in the JDK. You can study it later if you implement it. But the classloader can certainly load bytecode and execute commands. Here is just a brick to attract jade. I won't elaborate more. If you have better ideas, you can also put forward them and communicate together.

0x04 idea realization

Here we can think about a problem. How can we dynamically pass in parameters for execution? What you can think of must be reflection. If we use the above idea to convert all the code into bytecode, it doesn't make much sense. Because they are all fixed things, they will only execute and get the same execution result.

What I can think of here is to convert part of the fixed code in the code into bytecode, and then call it by reflection.

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


    }
}

Get results:

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

Now we have obtained the result, but we need to know how to restore the bytecode to string type.

When you look through the data later, you find that the string construction method can be executed directly. Let's take a look at his official documents.

Use bytes to construct a new string

code:

public class test {
    public static void main(String[] args) {
        byte[] bytes = new byte[]{106,82,117,116,105,101};
        String s = new String(bytes);
        System.out.println(s);
    }
}
public class test {
    public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException,illegalaccessexception,InvocationTargetException,InstantiationException,IOException {
        byte[] b1 = new byte[]{106,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);



    }
}

The command was executed successfully.

This is a complete piece of code, but there are still some places that are not handled well, such as:

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

Here is the reflection acquisition exec method, and the exec here is fixed. Exec is also strict for some devices.

So here you can handle it and convert it into bytecode.

Converted bytecode:

[101,120,99]

Improve the code:

public class test {
    public static void main(String[] args) throws ClassNotFoundException,IOException {
        String command = "ipconfig";
        byte[] b1 = new byte[]{106,101};
        String run = new String(b1);
        byte[] b2 = new byte[]{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,"gbk"); //字节输出流转换为字符
        System.out.println(ipconfig);

    }
}

In practice, don't use ipconfig and command to name them. These are sensitive words. This is just for ease of understanding.

In the real case, it should be request Getinputstream() to get the of the input command. Then you also need to pay attention to encryption during transmission, otherwise the traffic will certainly not pass the equipment.

0x05 end

In fact, the latter content is off the topic, because it is such a thing that suddenly comes to mind later. So write him down.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>