字節碼加密的需求
java的字節碼是可以反編譯的,所以很多時候,做商用產品的時候,防止別人看你的核心代碼是一個必要手段,字節碼加密的需求就誕生了,本質就是防止別人反編譯看代碼。加密的方式有很多,不是本文的主要目的。
如何破解字節碼加密
只要還是ibm,oracle,hp等通用的jvm,那麼字節碼加載到內存必須是jvm可以識別的東西。這裏簡單說一下字節碼加載到內存的代碼,就是classloader的loadclass方法,裏面有詳細的class文件到byte的做法,最後依靠defineclass方法加載到內存。下面列舉一下urlclassloadclass中調用的findclass方法,主要就是在找到類資源,然後define的過程。
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
其實大概也能猜到,字節碼解密的地方就在classloader中,defineClass已經是一個native方法了,他已經不能再拆了。這裏說的是上面的通用jvm,例如你自己改了openjdk的c++代碼,這個是可以做的,不過成本挺大。
這裏當然不是在說破解的方法要去找加載的classloader,這樣找代碼其實挺麻煩的,而且說不定對方在defineclass上做手腳。
這裏要介紹一個jvm提供的機制,javaagent。其實看到這裏,瞭解點javaagent的人就會有疑問,javaagent不是主要用來做字節碼動態修改的方式嗎?其實換個思路,我只要不改字節碼而是拿出來,是不是就說明,我破解了他的加密了呢。
如果不瞭解javaagent的並不影響,因爲代碼很簡單,我們看着,學會操作,慢慢體會。
動手寫代碼
public static void premain(String args,Instrumentation ins) {
ins.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//bootstrap類加載器加載的不管
if(loader==null){
return null;
}
String property = System.getProperty("dumpDir");
File file =new File(property+"/"+className+".class");
File parent = new File(file.getParent());
if(!parent.exists()){
parent.mkdirs();
}
OutputStream out =null;
try {
out =new FileOutputStream(file);
out.write(classfileBuffer);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
});
}
-
找個類寫上public static void premain(String args,Instrumentation ins),這個是agent的要求,和寫main方法是一個道理。
-
ClassFileTransformer就是我們的主體,加上這個以後,每次類的加載都會走到他的transform方法。
-
代碼的邏輯通俗易懂就是寫文件。最後必須return null,表示此次沒有字節碼改動。
MANIFEST.MF配置
Manifest-Version: 1.0
Premain-Class: xxx
Can-Redefine-Classes: true
Premain-Class填寫主類
運行
java -javaagent:jarpath -DdumpDir=path xxx
其實這裏主要是加入到jvm啓動參數就行,例如tomcat,你可以把-javaagent設置到java_options上。-DdumpDir是我自定義的屬性,爲了存放dump出來的字節碼。
換個角度思考
這就是一個agent使用場景,當不做字節碼改造的時候,就可以用來做做字節碼還原。以後還沒講其他的用法。