- 直接修改字节码,其实就是在很多问题上的降维打击;在许多领域有着广发应用,尤其是在AOP框架设计领域,它拥有着 反射调用 所无法比拟的性能优势;
- ASM即为一种直接修改字节码技术手段,它降低了开发者修改字节码的技术门槛, 但是实际上依然需要开发者对Java Class文件有着比较深刻的理解;
此前因为工作关系,使用了ASM去破解一个Java程序的验证,Hack也是直接修改字节码技术手段的一个应用(当然, 这是很低级的Hack, 防范的方法也很多);
下面举一个简单的例子,表述一下Hacker的核心过程;
要hack的类为Person.class, 从这个代码可以看出, 该方法的返回值应该永远是false;
public class Person {
private boolean passAuth = false;
public boolean validation() {
if(passAuth) {
System.out.println("pass auth");
} else {
System.out.println("fail auth");
}
return passAuth;
}
}
下面开始hack ,使用ASM 篡改字节码的过程;
- 从原始字节码, 修改为被伪造字节码的整个过程;
//读取编译ok的字节码;
Path p = Paths.get("F:\\src2020\\asmtest\\bin\\asmtest\\Person.class");
ClassReader cr = new ClassReader(Files.newInputStream(p));
//输出字节码的writer;
ClassWriter cw = new ClassWriter(cr, Opcodes.ASM4);
//构造一个责任链,ChangeClassAdapter是具体修改Class文件
ClassVisitor cv = new ChangeClassAdapter(cw);
//开始了链式调用,最后的结果会存在链条的最后一个,即cr;
cr.accept(cv, 0);
//输出伪造后的字节码
byte[] data = cw.toByteArray();
下面是具体修改字节码的核心类ChangeClassAdapter;
public class ChangeClassAdapter extends ClassVisitor {
public ChangeClassAdapter(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
// TODO Auto-generated constructor stub
}
public ChangeClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM4);
this.cv = cv;
}
public ChangeClassAdapter(int asm7) {
// TODO Auto-generated constructor stub
super(asm7);
}
@Override
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions)
{
//这里mv实际最终拿到的是MethodWriter
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (cv != null && "validation".equals(name)) {
//validation方法重新构造
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("i am hacker");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
//注意这里的返回值;只有返回null,才意味着修改是生效的;具体可以看一下源码,方法里直接有说明;
return null;
}
return mv;
}
}
将新的字节码保存到本地,并用JAD反编译出来看一下结果;
public class Person
{
public Person()
{
correct = false;
}
public boolean validation()
{
//成功hacker
System.out.println("i am hacker");
return true;
}
private boolean correct;
}
从ChangeClassAdapter 的例子可以看出, 对于复杂的逻辑修改,即使使用ASM,去在字节码层面重写代码,依然是一件并不容易的事情;
个人经验是使用ASM 的ASMifier,生成一个参考的模板代码;然后摘抄一些自己需要的部分;
- 写出自己期待的方法并编译;
- 用ASMifer生成构造该方法字节码的样板代码;
- 参考,摘抄自己需要的部分;