前言

Java是一种语言,JDK是Java这门语言的开发工具包,JDK= JRE + java
需要注意的是,Java 的版本号命名规则在 JDK 9 之后发生了变化。在 JDK 9 之前,版本号采用 “1.x.x” 的格式(如 1.8.0),而从 JDK 9 开始,版本号不再带有前缀的 “1”,而是直接使用主版本号作为前缀(如 9.0.4)。

jar包操作

1
2
jar -xf 你的jar包名称 #解压jar包
jar cf D:\jar包\temp\temp.jar . #当前目录中的所有文件和子目录打包成 JAR 文件

Java本地命令执行

Runtime对象可以调用exec()方法执行命令,同时他也是java命令执行编写最简单的方法,属于最外层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class JavaVersionExample {
public static void main(String[] args) {
try {
// 创建命令和参数数组
String[] command = { "net", "user" };
// 执行命令
Process process = Runtime.getRuntime().exec(command);
// 获取命令的输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待命令执行完成
process.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}

底层分析

Runtime

1
2
3
4
5
6
7
8
9
10
11
   public Process exec(String cmdarray[]) throws IOException {
return exec(cmdarray, null, null);
}
......
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir) //如果为 null,子进程将继承调用进程的当前工作目录
.start();
}

ProcessBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Process start() throws IOException {
String[] cmdarray = command.toArray(new String[command.size()]); //将一个 List<String> 转换为一个 String 数组(固定大小)
cmdarray = cmdarray.clone();
for (String arg : cmdarray)
if (arg == null)
throw new NullPointerException();
String prog = cmdarray[0];
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkExec(prog);
String dir = directory == null ? null : directory.toString();
for (int i = 1; i < cmdarray.length; i++) {
if (cmdarray[i].indexOf('\u0000') >= 0) {
throw new IOException("invalid null character in command");
}
}
try {
return ProcessImpl.start(cmdarray,
environment,
dir,
redirects,
redirectErrorStream);
}

ProcessImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
final class ProcessImpl extends Process {
//任何普通类中的 static 方法都可以通过类名直接调用
static Process start(String cmdarray[],
java.util.Map<String,String> environment,
String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream)
throws IOException
{
.................

return new ProcessImpl(cmdarray, envblock, dir,
stdHandles, redirectErrorStream);//调用构造方法

java 反射

Java反射操作的是java.lang.Class对象,在java反序列化中常用反射
反射Runtime执行本地命令代码片段:
System.out.println(org.apache.commons.io.IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 1.获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");
// 2.获取构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();//获取指定类的构造器对象,传入参数来指定构造器的**参数类型**
constructor.setAccessible(true);// 可访问标志表示是否屏蔽Java语言的访问检查,默认值是false,修改默认值,如此会屏蔽Java语言的(运行时)访问检查,使得对象的私有成员可以访问
// 3.创建示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();//通过构造器创建对象的实例,传参作为构造函数的**参数**



// 4.获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
//getMethod和getDeclaredMethod都能够获取到类成员方法,区别在于getMethod只能获取到当前类和父类的所有有权限的方法(如:public),而getDeclaredMethod能获取到当前类的所有成员方法(不包含父类)。
// 5.执行exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);



// 获取命令执行结果
InputStream in = process.getInputStream();
// 输出命令执行结果
System.out.println(org.apache.commons.io.IOUtils.toString(in, "UTF-8"));

//属性值
public class test {
public static void main(String[] args) throws Exception{
Class<?> stu = Class.forName("com.testforclass.Student");
Constructor<?> con2 = stu.getConstructor(String.class,int.class,String.class);
Object obj = con2.newInstance("aaa",18,"中国");
System.out.println(obj);

Field field = stu.getDeclaredField("name");
field.setAccessible(true);
String value = field.get(obj);//获取属性值
field.set(obj,"Arsene.Tang");//设置属性值

}
}

//修改被final关键字修饰的成员变量
// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");

// 设置modifiers修改权限
modifiers.setAccessible(true);

// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// 修改成员变量值
field.set(类实例对象, 修改后的值);

Java内省机制

内省(Introspection)通常是指使用反射机制来检测一个对象的属性和方法。内省主要用于JavaBeans,它允许开发者在运行时检查一个JavaBean的属性和方法。在Java中,内省机制通常与设计模式,如MVC,结合使用,以便动态获取对象信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class User {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

内省提供了操作 JavaBean 的 API。
Introspector 类:这是Java内省机制的核心类,提供了获取BeanInfo的静态方法。
BeanInfo 类:该类包含了关于Java Bean的元数据信息,如属性、事件和方法等。
PropertyDescriptor 类:该类描述了Java Bean的一个属性,包括其getter和setter方法。

1
2
3
4
5
6
7
BeanInfo info= Introspector.getBeanInfo(User.class);
// bean 信息
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
// 属性信息
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// 方法信息
MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();

Java 动态代理

Java 动态代理是一种在运行时动态生成代理对象的机制。通过使用 Java 提供的相关 API,可以在不修改原始类的情况下创建一个代理类,该代理类可以拦截原始类的方法调用,并在方法执行前后插入额外的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public interface flag {
void getflag(String flag);
}
public interface flag1 {
void getflag1();
}

public class realproxy implements InvocationHandler {//代理类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.toString().contains("java.lang.String")){//method为触发方法,args为参数
System.out.println("方法名为: "+method);
System.out.println("参数为: "+args[0]);
Object result = method.invoke(proxy, args);//若method.invoke调用了目标函数,代理结束后就不再调用目标函数
System.out.println("函数调用后");
}
else{
System.out.println("方法名为: "+method);
System.out.println("没有传入参数");
}
return null;
}
}

public class test {
public static void main(String[] args) {
InvocationHandler handler = new realproxy();//代理类对象,里面包含着具体的代理逻辑

flag flag = (flag) Proxy.newProxyInstance(test.class.getClassLoader(),// 指定动态代理类的类加载器
new Class[]{flag.class},// 定义动态代理生成的类实现的接口
handler);// 动态代理处理类
flag1 flag1 = (flag1) Proxy.newProxyInstance(test.class.getClassLoader(),new Class[]{flag1.class},handler);

flag.getflag("flag");//调用实现类中实现的接口,会触发代理类对象的invoke方法
flag1.getflag1();
}
}

设计模式

因为java是大二的时候学的,所以在写这篇博客时没有关注太基础的内容,由于后面代码审计过程经常遇到了这种设计思路,就详细写写

java 工厂类与实例

使用工厂类(Factory Class)相比传统的直接实例化(直接使用 new 关键字),就是将创建对象时需要初始化数据、调用其他服务的方法封装在一个类中,便于 集中管理和代码修改,实例就是工程类创造的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Step 1: 定义Animal接口
interface Animal {
void makeSound();
}

// Step 2: 创建具体的动物类实现接口
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}

class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow! Meow!");
}
}

// Step 3: 创建工厂类
class AnimalFactory {
public static Animal createAnimal(String type) {
if ("dog".equalsIgnoreCase(type)) {
return new Dog(); // 创建并返回Dog实例
} else if ("cat".equalsIgnoreCase(type)) {
return new Cat(); // 创建并返回Cat实例
} else {
return null; // 未知类型,返回null
}
}
}

// Step 4: 使用工厂类来创建实例
public class Main {
public static void main(String[] args) {
// 使用工厂类创建Dog实例
Animal dog = AnimalFactory.createAnimal("dog");
dog.makeSound(); // 输出: Woof! Woof!

// 使用工厂类创建Cat实例
Animal cat = AnimalFactory.createAnimal("cat");
cat.makeSound(); // 输出: Meow! Meow!
}
}

当然也可以将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类dogFactory和catFactory,这种模式叫做抽象工厂模式

Java Agent机制

在 jdk 1.5 之后引入了 java.lang.instrument 包,允许JVM在加载某个class文件之前对其字节码进行修改,同时也支持对已加载的class(类字节码)进行重新加载(Retransform),通过 java.lang.instrument 实现的工具我们称之为 Java Agent ,Java Agent 能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法

两种加载方式

1
2
3
4
5
6
7
//实现 premain 方法,在启动时进行加载 (该特性在 jdk 1.5 之后才有)
public static void premain(String args, Instrumentation inst) {}
//启动Java程序时添加-javaagent(Instrumentation API实现方式)或-agentpath/-agentlib(JVMTI的实现方式)参数,如java -javaagent:agent.jar[=options] -jar hello.jar

//实现 agentmain 方法,可以对运行中的Java进程附加Agent。(该特性在 jdk 1.6 之后才有)
public static void agentmain(String args, Instrumentation inst) {}
//使用com.sun.tools.attach.VirtualMachine#loadAgent方法加载指定jar包

使用jar命令打包应用程序

Java Agent限制了我们必须以jar包的形式运行或加载

1
2
3
4
5
6
7
8
9
10
11
Manifest-Version: 1.0
#创建一个 MF 文件,用于指定程序的入口类
Main-Class: Hello

#Premain-Class(Agent模式)或Agent-Class:(Agent模式)配置,如:
Premain-Class: com.anbai.sec.agent.CrackLicenseAgent
Agent-Class: com.anbai.sec.agent.CrackLicenseAgent

#如果需要修改已经被JVM加载过的类的字节码,那么还需要设置在MANIFEST.MF中添加
Can-Retransform-Classes: true
#或Can-Redefine-Classes: true

然后就可以利用 编写的mf文件 编译jar包了

jar cvfm hello.jar hello.mf Hello.class

动态修改字节码

Instrumentation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Instrumentation {
// 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。

// addTransformer方法配置之后,后续的类加载都会被Transformer拦截
void addTransformer(ClassFileTransformer transformer);

// 删除一个类转换器
boolean removeTransformer(ClassFileTransformer transformer);

// 对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

// 判断目标类支持reTyansformer
boolean isModifiableClass(Class<?> theClass);

// 获取所以已经加载的类。
@SuppressWarnings("rawtypes")
Class[] getAllLoadedClasses();

//检测是否允许reTransformer
boolean isRetransformClassesSupported() ;
}

InstrumentationImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private final TransformerManager mTransformerManager = new TransformerManager(false);
.....
public void addTransformer(ClassFileTransformer var1) {
this.addTransformer(var1, false);
}
public synchronized void addTransformer(ClassFileTransformer var1, boolean var2) {
if (var1 == null) {
throw new NullPointerException("null passed as 'transformer' in addTransformer");
} else {
if (var2) {//var2需要为true
if (!this.isRetransformClassesSupported()) {
throw new UnsupportedOperationException("adding retransformable transformers is not supported in this environment");
}

if (this.mRetransfomableTransformerManager == null) {
this.mRetransfomableTransformerManager = new TransformerManager(true);
}

this.mRetransfomableTransformerManager.addTransformer(var1);
if (this.mRetransfomableTransformerManager.getTransformerCount() == 1) {
this.setHasRetransformableTransformers(this.mNativeAgent, true);
}
} else {
this.mTransformerManager.addTransformer(var1);
}

}
}

ClassFileTransformer

当有新的类被JVM加载时JVM会自动回调用我们自定义的Transformer类的transform方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ClassFileTransformer {

/**
* 类文件转换方法,重写transform方法可获取到待加载的类相关信息
*
* @param loader 定义要转换的类加载器;如果是被Bootstrap ClassLoader(引导类加载器)所加载那么loader参数的值是空
* @param className 传入类的类名,如:java/lang/Runtime
* @param classBeingRedefined 如果是被重定义或重转换触发,则为重定义或重转换的类;如果是类加载,则为 null
* @param protectionDomain 要定义或重定义的类的保护域
* @param classfileBuffer 类文件格式的输入字节缓冲区(不得修改)
* @return 字节码byte数组。
*/
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer);

}

启动后加载 agent

要上传的jar包内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.instrument.Instrumentation;

public class AgentMain {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain"; //ApplicationFilterchain#dofilter 方法是一定会被调用的,并且有请求的 request 和 response

public static void agentmain(String agentArgs, Instrumentation ins) {
ins.addTransformer(new DefineTransformer(),true);//DefineTransformer为我们构造的恶意代码

Class[] classes = ins.getAllLoadedClasses();
for (Class clas:classes){
if (clas.getName().equals(ClassName)){
try{
// 如果想要改变的类已经加载过了,需要对想要修改的类进行重新定义
ins.retransformClasses(new Class[]{clas});
} catch (Exception e){
e.printStackTrace();
}
}
}
}
}
public class DefineTransformer implements ClassFileTransformer { }

命令执行内容

1
2
3
4
5
6
7
8
9
10
11
String path = "AgentMain.jar的路径";
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor v:list){
System.out.println(v.displayName());
if (v.displayName().contains("AgentMainDemo")){
// 将 jvm 虚拟机 attach 指定的进程
VirtualMachine vm = VirtualMachine.attach(v.id());
// 向jvm注册一个代理程序agent
vm.loadAgent(path);
vm.detach();//断开与 JVM 的连接
}