Javassist与RASP实现详解
一、Javassist基础
1.1 Javassist简介
Javassist (JAVA programming ASSISTant) 是一个用于在Java中编辑字节码的类库,它使Java程序能够在运行时定义新类,并在JVM加载时修改类文件。与反射类似,但开销更低。
1.2 核心API
ClassPool相关
getDefault(): 返回默认的单例ClassPoolappendClassPath,insertClassPath: 添加类搜索路径get(),getCtClass(): 根据类路径获取CtClass对象makeClass(): 创建新类
CtClass操作
freeze(): 冻结类使其不可修改isFrozen(): 判断类是否被冻结defrost(): 解冻类使其可修改prune(): 删除不必要的属性减少内存占用detach(): 从ClassPool中删除类writeFile(): 生成.class文件toClass(): 通过类加载器加载CtClass
CtMethod操作
insertBefore(): 在方法起始位置插入代码insertAfter(): 在所有return前插入代码insertAt(): 在指定位置插入代码setBody(): 设置方法体内容make(): 创建新方法
1.3 基本操作示例
1. 创建类
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.makeClass("Javassist.Hello");
ctClass.writeFile();
2. 添加属性
CtField name = new CtField(cp.get("java.lang.String"), "name", ctClass);
name.setModifiers(Modifier.PUBLIC);
ctClass.addField(name, CtField.Initializer.constant("Sentiment"));
3. 添加方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "Hello1",
new CtClass[]{CtClass.intType, CtClass.charType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("System.out.println(\"This is test !\");");
ctClass.addMethod(ctMethod);
4. 添加构造器
CtConstructor cons = new CtConstructor(
new CtClass[]{cp.getCtClass("java.lang.String")}, ctClass);
cons.setBody("{name=\"Sentiment\";}");
ctClass.addConstructor(cons);
5. 修改已有类
CtClass ctClass = cp.get("Javassist.Test");
CtConstructor test = ctClass.getConstructors()[0];
test.setBody("{System.out.println(\"Changing.\");}");
ctClass.writeFile();
6. 加载字节码
ctClass.toBytecode();
ctClass.toClass().newInstance();
1.4 特殊变量
| 标识符 | 作用 |
|---|---|
$0, $1, $2... |
$0代表this,$1、$2代表方法参数 |
$args |
方法参数数组(Object[]) |
| ` |
\[` | 所有方法参数简写 |
| `$r` | 返回结果类型(用于强制转换) |
| `$_` | 方法返回值 |
| `$sig` | 参数类型对象数组 |
| `$type` | 返回值类型 |
| `$class` | 正在修改的类 |
## 二、RASP实现
### 2.1 RASP简介
RASP(Runtime application self-protection)是应用程序运行时防护技术,与传统WAF的主要区别在于防护层级更底层,能在功能调用前或调用时获取方法参数等信息进行安全判定。
#### RASP与WAF对比
**优点:**
1. 误报率低:基于应用程序上下文精确分析
2. 保护全面:能监控输入和输出
**缺点:**
1. 性能损耗(约5%)
2. 部署成本高(需针对不同技术栈)
### 2.2 双亲委派问题
Java类加载采用双亲委派机制:
1. BootstrapClassLoader(C++实现)
2. ExtClassLoader(父类为null)
3. AppClassLoader(父类为ExtClassLoader)
默认情况下,premain和agentmain由AppClassLoader加载,当需要修改BootstrapClassLoader加载的类时会出现问题。
**解决方案:**
```java
inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath));
```
### 2.3 RASP实现示例
#### 目标:Hook ProcessBuilder执行cmd
**Main.java**
```java
public class Main {
public static void main(String[] args) throws IOException {
ProcessBuilder command = new ProcessBuilder().command("cmd", "/c", "chdir");
Process process = command.start();
InputStream inputStream = process.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
System.out.println(bufferedReader.readLine());
}
}
```
**PreMainDemo.java**
```java
public class PreMainDemo {
public static void premain(String agentArgs, Instrumentation inst)
throws IOException, UnmodifiableClassException {
// 先加载ProcessBuilder类
ProcessBuilder processBuilder = new ProcessBuilder();
Class[] classes = inst.getAllLoadedClasses();
for (Class aClass : classes) {
if (aClass.getName().equals("java.lang.ProcessBuilder")
&& inst.isModifiableClass(aClass)) {
inst.addTransformer(new TransformerDemo(), true);
inst.retransformClasses(aClass);
}
}
}
}
```
**TransformerDemo.java**
```java
public class TransformerDemo implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className,
Class> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
byte[] bytes = null;
if (className.equals("java/lang/ProcessBuilder")) {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = null;
try {
ctClass = cp.get("java.lang.ProcessBuilder");
CtMethod[] methods = ctClass.getMethods();
String source = "if ($0.command.get(0).equals(\"cmd\")){\n" +
" System.out.println(\"Dangerous!\");\n" +
" System.out.println($0);\n" +
" return null;\n" +
"}";
for (CtMethod method : methods) {
if (method.getName().equals("start")) {
method.insertBefore(source);
break;
}
}
bytes = ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
}
return bytes;
}
}
```
### 2.4 打包配置
**Main.jar的pom配置**
```xml
);");
}
}
});
### 3.2 方法调用前后插入代码
```java
ctMethod.insertBefore("System.out.println(\"我在前面插入:\"+$1);");
ctMethod.insertAfter("System.out.println(\"我在后面插入了:\"+$2);");
四、总结
本文详细介绍了Javassist的基本用法和RASP的实现原理,包括:
- Javassist的核心API和基本操作
- RASP的核心概念和实现方式
- 双亲委派问题及解决方案
- 完整的RASP示例实现
- 高级代码修改技巧
通过Javassist可以灵活地修改字节码,结合Java Agent技术可以实现强大的运行时防护功能。