- 直接修改字節(jié)碼,其實就是在很多問題上的降維打擊;在許多領域有著廣發(fā)應用,尤其是在AOP框架設計領域,它擁有著 反射調(diào)用 所無法比擬的性能優(yōu)勢;
- ASM即為一種直接修改字節(jié)碼技術手段,它降低了開發(fā)者修改字節(jié)碼的技術門檻, 但是實際上依然需要開發(fā)者對Java Class文件有著比較深刻的理解;
此前因為工作關系,使用了ASM去破解一個Java程序的驗證,Hack也是直接修改字節(jié)碼技術手段的一個應用(當然, 這是很低級的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 篡改字節(jié)碼的過程;
- 從原始字節(jié)碼, 修改為被偽造字節(jié)碼的整個過程;
//讀取編譯ok的字節(jié)碼;
Path p = Paths.get("F:\\src2020\\asmtest\\bin\\asmtest\\Person.class");
ClassReader cr = new ClassReader(Files.newInputStream(p));
//輸出字節(jié)碼的writer;
ClassWriter cw = new ClassWriter(cr, Opcodes.ASM4);
//構造一個責任鏈,ChangeClassAdapter是具體修改Class文件
ClassVisitor cv = new ChangeClassAdapter(cw);
//開始了鏈式調(diào)用,最后的結果會存在鏈條的最后一個,即cr;
cr.accept(cv, 0);
//輸出偽造后的字節(jié)碼
byte[] data = cw.toByteArray();
下面是具體修改字節(jié)碼的核心類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;
}
}
將新的字節(jié)碼保存到本地,并用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,去在字節(jié)碼層面重寫代碼,依然是一件并不容易的事情;
個人經(jīng)驗是使用ASM 的ASMifier,生成一個參考的模板代碼;然后摘抄一些自己需要的部分;
- 寫出自己期待的方法并編譯;
- 用ASMifer生成構造該方法字節(jié)碼的樣板代碼;
- 參考,摘抄自己需要的部分;