class很好反編譯,因此須要對class文件先進行加密,而後使用本身的classloader進行解密並加載。html
【步驟】java
大概分兩步:spring
1.對class文件進行加密express
2.寫解密class文件並加載的classloaderapache
3.將這個classloader加入到tomcat中,也就是使tomcat能夠調用到這個classloader小程序
【加密】tomcat
1.思路app
字節流讀取class文件,進行簡單的移位less
2.實現eclipse
作了一個小程序,實現了對某文件夾下全部class文件字節流讀取,並+2位的加密方式
3.說明
swing是使用myeclipse的插件作的,可能比較亂
【classloader】
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoader; /** * 本身的ClassLoader * 用於解密加密過的class文件並加載 * @author uikoo9 */ public class MyClassLoader extends WebappClassLoader{ /** * 默認構造器 */ public MyClassLoader() { super(); } /** * 默認構造器 * @param parent */ public MyClassLoader(ClassLoader parent) { super(parent); } /* (non-Javadoc) * @see org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String) */ public Class<?> findClass(String name) throws ClassNotFoundException { byte[] classBytes = null; try { classBytes = loadClassBytes(name); } catch (Exception e) { throw new ClassNotFoundException(name); } Class<?> cl= defineClass(name, classBytes, 0, classBytes.length); if(cl == null) throw new ClassNotFoundException(name); return cl; } /** * 簡單的解密 * @param name * @return * @throws IOException */ private byte[] loadClassBytes(String name) throws IOException{ String cname = name.replace('.', '/') + ".class"; FileInputStream in = new FileInputStream(cname); try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int ch; while((ch = in.read()) != -1){ if(cname.contains("uikoo9")){// 若是包含uikoo9說明是本身寫的class,進行解密 System.out.println("++"); buffer.write((byte)(ch-2)); }else{ buffer.write((byte)ch); } } in.close(); return buffer.toByteArray(); }finally{ in.close(); } } }
【加入到tomcat中】
1.網上
網上不少文章都問到tomcat怎麼使用本身的classloader,可是說明白的幾乎沒有,
最後本身讀了tomcat官網的文檔,找到了答案,
地址:http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html
2.方法
說簡單點,就是在tomcat\conf\context.xml中添加如下這段代碼:
<Loader loaderClass="com.uikoo9.MyClassLoader"></Loader >
3.classloader
可是注意,這裏的com.uikoo9.MyClassLoader並非項目中的,
而是須要放到tomcat\lib下。
【新的問題】
1.這個本身寫的classloader確實起做用的,可是問題也隨之而來,
原來tomcat在調用classloader以前會調用一個本身的classparser類來對class文件進行解析
2.classparser
位於org\apache\tomcat\util\bcel\classfile下的ClassParser.java,
源代碼:
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tomcat.util.bcel.classfile; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.tomcat.util.bcel.Constants; /** * Wrapper class that parses a given Java .class file. The method <A * href ="#parse">parse</A> returns a <A href ="JavaClass.html"> * JavaClass</A> object on success. When an I/O error or an * inconsistency occurs an appropiate exception is propagated back to * the caller. * * The structure and the names comply, except for a few conveniences, * exactly with the <A href="ftp://java.sun.com/docs/specs/vmspec.ps"> * JVM specification 1.0</a>. See this paper for * further details about the structure of a bytecode file. * * @version $Id: ClassParser.java 992409 2010-09-03 18:35:59Z markt $ * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A> */ public final class ClassParser { private DataInputStream file; private boolean fileOwned; private String file_name; private String zip_file; private int class_name_index, superclass_name_index; private int major, minor; // Compiler version private int access_flags; // Access rights of parsed class private int[] interfaces; // Names of implemented interfaces private ConstantPool constant_pool; // collection of constants private Field[] fields; // class fields, i.e., its variables private Method[] methods; // methods defined in the class private Attribute[] attributes; // attributes defined in the class private boolean is_zip; // Loaded from zip file private static final int BUFSIZE = 8192; /** * Parse class from the given stream. * * @param file Input stream * @param file_name File name */ public ClassParser(InputStream file, String file_name) { this.file_name = file_name; fileOwned = false; String clazz = file.getClass().getName(); // Not a very clean solution ... is_zip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar."); if (file instanceof DataInputStream) { this.file = (DataInputStream) file; } else { this.file = new DataInputStream(new BufferedInputStream(file, BUFSIZE)); } } /** * Parse the given Java class file and return an object that represents * the contained data, i.e., constants, methods, fields and commands. * A <em>ClassFormatException</em> is raised, if the file is not a valid * .class file. (This does not include verification of the byte code as it * is performed by the java interpreter). * * @return Class object representing the parsed class file * @throws IOException * @throws ClassFormatException */ public JavaClass parse() throws IOException, ClassFormatException { ZipFile zip = null; try { if (fileOwned) { if (is_zip) { zip = new ZipFile(zip_file); ZipEntry entry = zip.getEntry(file_name); if (entry == null) { throw new IOException("File " + file_name + " not found"); } file = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry), BUFSIZE)); } else { file = new DataInputStream(new BufferedInputStream(new FileInputStream( file_name), BUFSIZE)); } } /****************** Read headers ********************************/ // Check magic tag of class file readID(); // Get compiler version readVersion(); /****************** Read constant pool and related **************/ // Read constant pool entries readConstantPool(); // Get class information readClassInfo(); // Get interface information, i.e., implemented interfaces readInterfaces(); /****************** Read class fields and methods ***************/ // Read class fields, i.e., the variables of the class readFields(); // Read class methods, i.e., the functions in the class readMethods(); // Read class attributes readAttributes(); // Check for unknown variables //Unknown[] u = Unknown.getUnknownAttributes(); //for(int i=0; i < u.length; i++) // System.err.println("WARNING: " + u[i]); // Everything should have been read now // if(file.available() > 0) { // int bytes = file.available(); // byte[] buf = new byte[bytes]; // file.read(buf); // if(!(is_zip && (buf.length == 1))) { // System.err.println("WARNING: Trailing garbage at end of " + file_name); // System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf)); // } // } } finally { // Read everything of interest, so close the file if (fileOwned) { try { if (file != null) { file.close(); } if (zip != null) { zip.close(); } } catch (IOException ioe) { //ignore close exceptions } } } // Return the information we have gathered in a new object return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor, access_flags, constant_pool, interfaces, fields, methods, attributes); } /** * Read information about the attributes of the class. * @throws IOException * @throws ClassFormatException */ private final void readAttributes() throws IOException, ClassFormatException { int attributes_count; attributes_count = file.readUnsignedShort(); attributes = new Attribute[attributes_count]; for (int i = 0; i < attributes_count; i++) { attributes[i] = Attribute.readAttribute(file, constant_pool); } } /** * Read information about the class and its super class. * @throws IOException * @throws ClassFormatException */ private final void readClassInfo() throws IOException, ClassFormatException { access_flags = file.readUnsignedShort(); /* Interfaces are implicitely abstract, the flag should be set * according to the JVM specification. */ if ((access_flags & Constants.ACC_INTERFACE) != 0) { access_flags |= Constants.ACC_ABSTRACT; } if (((access_flags & Constants.ACC_ABSTRACT) != 0) && ((access_flags & Constants.ACC_FINAL) != 0)) { throw new ClassFormatException("Class " + file_name + " can't be both final and abstract"); } class_name_index = file.readUnsignedShort(); superclass_name_index = file.readUnsignedShort(); } /** * Read constant pool entries. * @throws IOException * @throws ClassFormatException */ private final void readConstantPool() throws IOException, ClassFormatException { constant_pool = new ConstantPool(file); } /** * Read information about the fields of the class, i.e., its variables. * @throws IOException * @throws ClassFormatException */ private final void readFields() throws IOException, ClassFormatException { int fields_count; fields_count = file.readUnsignedShort(); fields = new Field[fields_count]; for (int i = 0; i < fields_count; i++) { fields[i] = new Field(file, constant_pool); } } /******************** Private utility methods **********************/ /** * Check whether the header of the file is ok. * Of course, this has to be the first action on successive file reads. * @throws IOException * @throws ClassFormatException */ private final void readID() throws IOException, ClassFormatException { int magic = 0xCAFEBABE; if (file.readInt() != magic) { throw new ClassFormatException(file_name + " is not a Java .class file"); } } /** * Read information about the interfaces implemented by this class. * @throws IOException * @throws ClassFormatException */ private final void readInterfaces() throws IOException, ClassFormatException { int interfaces_count; interfaces_count = file.readUnsignedShort(); interfaces = new int[interfaces_count]; for (int i = 0; i < interfaces_count; i++) { interfaces[i] = file.readUnsignedShort(); } } /** * Read information about the methods of the class. * @throws IOException * @throws ClassFormatException */ private final void readMethods() throws IOException, ClassFormatException { int methods_count; methods_count = file.readUnsignedShort(); methods = new Method[methods_count]; for (int i = 0; i < methods_count; i++) { methods[i] = new Method(file, constant_pool); } } /** * Read major and minor version of compiler which created the file. * @throws IOException * @throws ClassFormatException */ private final void readVersion() throws IOException, ClassFormatException { minor = file.readUnsignedShort(); major = file.readUnsignedShort(); } }
3.問題
發現這個解析類的文件會先去判斷class的頭信息來肯定是否是class文件,
可是因爲咱們對class進行了加密,因此頭信息變了,因此這個解析class文件的類會報錯,也就不會調用到classloader了。
【繼續】
文章有點長,不知道有人有耐心看完不。
1.上面的問題折騰了一天,才發現是本身解密的部分有問題,
2.不過也是有收穫的,發現自定寫的loader只能加載非class的文件,而不能加載class
3.意思就是說,你須要將原來的class文件加密並改變文件後綴,而後配合本身的loader使用
【delegate】
因爲本身英語水平有限,因此以前的tomcat文章只知其一;不知其二,
經過今天的研究發現context.xml中的delegate屬性的用法。
1.loader的代碼:
package com.uikoo9.loader; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoader; /** * 自定義的classloader * 能夠解密文件並加載 * @author uikoo9 */ public class UClassLoader extends WebappClassLoader{ /** * 默認構造器 */ public UClassLoader() { super(); } /** * 默認構造器 * @param parent */ public UClassLoader(ClassLoader parent) { super(parent); } /* (non-Javadoc) * @see org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String) */ public Class<?> findClass(String name) throws ClassNotFoundException { byte[] classBytes = null; try { if(name.contains("uikoo9")){ System.out.println("++++++" + name); classBytes = loadClassBytesEncrypt(name); }else{ System.out.println("-------" + name); classBytes = loadClassBytesDefault(name); } } catch (Exception e) { e.printStackTrace(); } Class<?> cl = defineClass(name, classBytes, 0, classBytes.length); if (cl == null) throw new ClassNotFoundException(name); return cl; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if(name.contains("uikoo9")){ return findClass(name); }else{ return super.loadClass(name); } } /** * 加載加密後的class字節流 * @param name * @return * @throws IOException */ private byte[] loadClassBytesEncrypt(String name) throws IOException { String cname = name.replace('.', '/') + ".uikoo9"; FileInputStream in = null; in = new FileInputStream(cname); try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { buffer.write((byte)(ch - 2)); } in.close(); return buffer.toByteArray(); } finally { in.close(); } } /** * 加載普通的class字節流 * @param name * @return * @throws IOException */ private byte[] loadClassBytesDefault(String name) throws IOException { String cname = name.replace('.', '/') + ".class"; FileInputStream in = null; in = new FileInputStream(cname); try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { buffer.write((byte)ch); } in.close(); return buffer.toByteArray(); } finally { in.close(); } } }
2.delegate="false"時,啓動tomcat:
<Loader loaderClass="com.uikoo9.loader.UClassLoader" delegate="false"></Loader >
3.delegate="true"時,啓動tomcat:
<Loader loaderClass="com.uikoo9.loader.UClassLoader" delegate="true"></Loader >
4.總結
delegate爲true的時候自定義的loader只用來加載本身的代碼
【新問題】
以上的代碼整理一下,啓動tomcat,沒有報錯,
可是當點擊頁面的時候,也就是向後臺請求的時候依然報錯,
【end】
通過中午的掙扎,這個問題終於解決了,
注意,這個只適合沒有spring的,由於spring有本身的classloader。
【classloader】
1.代碼:
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import org.apache.catalina.loader.WebappClassLoader; /** * 自定義的classloader * 能夠解密文件並加載 * @author uikoo9 */ public class UClassLoader extends WebappClassLoader{ /** * 默認構造器 */ public UClassLoader() { super(); } /** * 默認構造器 * @param parent */ public UClassLoader(ClassLoader parent) { super(parent); } /* (non-Javadoc) * @see org.apache.catalina.loader.WebappClassLoader#findClass(java.lang.String) */ public Class<?> findClass(String name) throws ClassNotFoundException { if(name.contains("uikoo9")){ return findClassEncrypt(name); }else{ return super.findClass(name); } } /** * 查找class * @param name * @return * @throws ClassNotFoundException */ private Class<?> findClassEncrypt(String name) throws ClassNotFoundException{ byte[] classBytes = null; try { System.out.println("++++++" + name); classBytes = loadClassBytesEncrypt(name); } catch (Exception e) { e.printStackTrace(); } Class<?> cl = defineClass(name, classBytes, 0, classBytes.length); if (cl == null) throw new ClassNotFoundException(name); return cl; } /** * 加載加密後的class字節流 * @param name * @return * @throws IOException */ private byte[] loadClassBytesEncrypt(String name) throws IOException { String basepath = "Z:/program/workspaces/_work_03_bzb/WebRoot/WEB-INF/classes/";// 項目物理地址 String cname = basepath + name.replace('.', '/') + ".uikoo9"; System.out.println(cname); FileInputStream in = new FileInputStream(cname); try { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int ch; while ((ch = in.read()) != -1) { buffer.write((byte)(ch - 2)); } in.close(); return buffer.toByteArray(); } finally { in.close(); } } }
【加入到tomcat】
1.context.xml
找到tomcat下contex.xml,在context之間加入如下代碼:
<Loader loaderClass="com.uikoo9.loader.UClassLoader" delegate="true"></Loader>
其中loaderClass就是本身寫loader,delegate=「true」的意思是隻解密非系統的class和jar
2.添加loader
將本身寫的loader的class文件放到tomcat\lib下
【開始】
1.使用加密程序對classes下全部文件加密,加密以後全部的class文件後綴變爲uikoo9,能夠本身修改源代碼
2.將原classes文件夾刪除,將加密後的classes文件夾複製進去
3.修改context.xml
4.tomcat\lib下添加loader.class
5.啓動tomcat
原文:http://blog.csdn.net/uikoo9/article/details/17281403