教你一招破解字節碼加密 頂 原

字節碼加密的需求

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;
			}
		});
	}
	
  1. 找個類寫上public static void premain(String args,Instrumentation ins),這個是agent的要求,和寫main方法是一個道理。

  2. ClassFileTransformer就是我們的主體,加上這個以後,每次類的加載都會走到他的transform方法。

  3. 代碼的邏輯通俗易懂就是寫文件。最後必須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使用場景,當不做字節碼改造的時候,就可以用來做做字節碼還原。以後還沒講其他的用法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章