问:
如果我把我的class文件加密,在运行时用指定的类加载器(class loader)装入并解密它,这样子能防止被反编译吗?
答:
防止JAVA字节码反编译这个问题在java语言雏形期就有了,尽管市面上存在一些反编译的工具可以利用,但是JAVA程序员还是不断的努力寻找新的更有效的方法来保护他们的智慧结晶。在此,我将详细给大家解释这一直来在论坛上有争议的话题。
Class文件能被很轻松的重构生成JAVA源文件与最初JAVA字节码的设计目的和商业交易有紧密地联系。另外,JAVA字节码被设计成简洁、平台独立性、网络灵活性,并且易于被字节码解释器和JIT (just-in-time)/HotSpot 编译器所分析。可以清楚地了解程序员的目的, Class文件要比JAVA源文件更易于分析。
如果不能阻止被反编译的话,至少可以通过一些方法来增加它的困难性。例如: 在一个分步编译里,你可以打乱Class文件的数据以使其难读或者难以被反编译成正确的JAVA源文件,前者可以采用极端函数重载,后者用操作控制流建立控制结构使其难以恢复正常次序。有更多成功的商业困惑者采用这些或其他的技术来保护自己的代码。
不幸的是,哪种方法都必须改变JVM运行的代码,并且许多用户害怕这种转化会给他们的程序带来新的Bug。而且,方法和字段重命名会调用反射从而使程序停止工作,改变类和包的名字会破坏其他的JAVA APIS(JNDI, URL providers, etc),除了改变名字,如果字节码偏移量和源代码行数之间的关系改变了,在恢复这有异常的堆栈将很困难。
于是就有了一些打乱JAVA源代码的选项,但是这将从本质上导致一系列问题的产生。
加密而不打乱
或许上述可能会使你问,假如我把字节码加密而不是处理字节码,并且JVM运行时自动将它解密并装入类加载器,然后JVM运行解密后的字节码文件,这样就不会被反编译了对吗?
考虑到你是第一个提出这种想法的并且它又能正常运行,我表示遗憾和不幸,这种想法是错误的。
下面是一个简单的类编码器:
为了阐明这种思想,我采用了一个实例和一个很通用的类加载器来运行它,该程序包括两个类:
public class Main
{
public static void main (final String [] args)
{
System.out.println ("secret result = " + MySecretClass.mySecretAlgorithm ());
}
} // End of class
package my.secret.code;
import java.util.Random;
public class MySecretClass
{
/**
* Guess what, the secret algorithm just uses a random number generator...
*/
public static int mySecretAlgorithm ()
{
return (int) s_random.nextInt ();
}
private static final Random s_random = new Random (System.currentTimeMillis ());
} // End of class
我想通过加密相关的class文件并在运行期解密来隐藏my.secret.code.MySecretClass的执行。用下面这个工具可以达到效果(你可以到这里下载Resources):
public class EncryptedClassLoader extends URLClassLoader
{
public static void main (final String [] args)
throws Exception
{
if ("-run".equals (args [0]) && (args.length >= 3))
{
// Create a custom loader that will use the current loader as
// delegation parent:
final ClassLoader appLoader =
new EncryptedClassLoader (EncryptedClassLoader.class.getClassLoader (),
new File (args [1]));
// Thread context loader must be adjusted as well:
Thread.currentThread ().setContextClassLoader (appLoader);
final Class app = appLoader.loadClass (args [2]);
final Method appmain = app.getMethod ("main", new Class [] {String [].class});
final String [] appargs = new String [args.length - 3];
System.arraycopy (args, 3, appargs, 0, appargs.length);
appmain.invoke (null, new Object [] {appargs});
}
else if ("-encrypt".equals (args [0]) && (args.length >= 3))
{
... encrypt specified classes ...
}
else
throw new IllegalArgumentException (USAGE);
}
/**
* Overrides java.lang.ClassLoader.loadClass() to change the usual parent-child
* delegation rules just enough to be able to "snatch" application classes
* from under system classloader's nose.
*/
public Class loadClass (final String name, final boolean resolve)
throws ClassNotFoundException
{
if (TRACE) System.out.println ("loadClass (" + name + ", " + resolve + ")");
Class c = null;
// First, check if this class has already been defined by this classloader
// instance:
c = findLoadedClass (name);
if (c == null)
{
Class parentsVersion = null;
try
{
// This is slightly unorthodox: do a trial load via the
// parent loader and note whether the parent delegated or not;
// what this accomplishes is proper delegation for all core
// and extension classes without my having to filter on class name:
parentsVersion = getParent ().loadClass (name);
if (parentsVersion.getClassLoader () != getParent ())
c = parentsVersion;
}
catch (ClassNotFoundException ignore) {}
catch (ClassFormatError ignore) {}
if (c == null)
{
try
{
// OK, either 'c' was loaded by the system (not the bootstrap
// or extension) loader (in which case I want to ignore that
// definition) or the parent failed altogether; either way I
// attempt to define my own version:
c = findClass (name);
}
catch (ClassNotFoundException ignore)
{
// If that failed, fall back on the parent's version
// [which could be null at this point]:
c = parentsVersion;
}
}
}
if (c == null)
throw new ClassNotFoundException (name);
if (resolve)
resolveClass (c);
return c;
}
/**
* Overrides java.new.URLClassLoader.defineClass() to be able to call
* crypt() before defining a class.
*/
protected Class findClass (final String name)
throws ClassNotFoundException
{
if (TRACE) System.out.println ("findClass (" + name + ")");
// .class files are not guaranteed to be loadable as resources;
// but if Sun's code does it, so perhaps can mine...
final String classResource = name.replace ('.', '/') + ".class";
final URL classURL = getResource (classResource);
if (classURL == null)
throw new ClassNotFoundException (name);
else
{
InputStream in = null;
try
{
in = classURL.openStream ();
final byte [] classBytes = readFully (in);
// "decrypt":
crypt (classBytes);
if (TRACE) System.out.println ("decrypted [" + name + "]");
return defineClass (name, classBytes, 0, classBytes.length);
}
catch (IOException ioe)
{
throw new ClassNotFoundException (name);
}
finally
{
if (in != null) try { in.close (); } catch (Exception ignore) {}
}
}
}
/**
* This classloader is only capable of custom loading from a single directory.
*/
private EncryptedClassLoader (final ClassLoader parent, final File classpath)
throws MalformedURLException
{
super (new URL [] {classpath.toURL ()}, parent);
if (parent == null)
throw new IllegalArgumentException ("EncryptedClassLoader" +
" requires a non-null delegation parent");
}
/**
* De/encrypts binary data in a given byte array. Calling the method again
* reverses the encryption.
*/
private static void crypt (final byte [] data)
{
for (int i = 8; i < data.length; ++ i) data [i] ^= 0x5A;
}
... more helper methods ...
} // End of class
这个累加载器(EncryptedClassLoader)有两个基本的操作,在给定的类路径下加密一系列Class文件并且运行一个先前加密的程序。加密后的文件很简单,有一些极讨厌的各个字节的位组成。(当然,XOR运算符不可能被加密,这只是一个范例,请多多包涵。)
通过EncryptedClassLoader来加载类需要注意一些问题,我实现的是继承自java.net.URLClassLoader并且重载了loadClass()和defineClass()两个方法来实现自己的两个功能。一个是专心于JAVA 2 类加载器的委托规则并且在系统类加载器做之前先加载一个经加密过的类;二是在执行defineClass()之前立即调用crypt()方法,否则会执行URLClassLoader.findClass()。
执行下面的语句:
>javac -d bin src/*.java src/my/secret/code/*.java
我把Main.class和MySecretClass.class进行了.加密:
>java -cp bin EncryptedClassLoader -encrypt bin Main my.secret.code.MySecretClass
encrypted [Main.class]
encrypted [my\secret\code\MySecretClass.class]
现在原先编译的class文件已经被加密后的文件所替代了,如果我想运行原始类文件,需要使用EncryptedClassLoader来操作:
>java -cp bin Main
Exception in thread "main" java.lang.ClassFormatError: Main (Illegal constant pool type)
at java.lang.ClassLoader.defineClass0(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:502)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:250)
at java.net.URLClassLoader.access$100(URLClassLoader.java:54)
at java.net.URLClassLoader$1.run(URLClassLoader.java:193)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:186)
at java.lang.ClassLoader.loadClass(ClassLoader.java:299)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:265)
at java.lang.ClassLoader.loadClass(ClassLoader.java:255)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:315)
>java -cp bin EncryptedClassLoader -run bin Main
decrypted Main
decrypted [my.secret.code.MySecretClass]
secret result = 1362768201
现在可以确信,采用任何反编译工具对加密后的Class文件都不会起作用的。
现在添加一个可靠的密码保护机制,把它打包成本地可执行文件,并且使其对外收费。这样子可以吗?当然不能这样了。
ClassLoader.defineClass():必然经过的接口
所有的类加载器必须经过明确地API把类定义传递到JVM里,这就需要java.lang.ClassLoader.defineClass()方法了。类加载器的API有多个这个方法的重载,但是所有的方法都会调用defineClass(String, byte[], int, int, ProtectionDomain),这是一个在经过一些简单验证后放入到JVM里的最终的方法。如果你想建立一个新的Class文件的话,这对于理解每个类加载器都会不可避免的调用该方法是很重要的。
你只能在方法defineClass()里把一些单调的字节数组生成Class对象,并且我们猜测这些字节数组文件会包含一些文档格式化(查看class文件格式规范well-document.d format)的未加密的class定义,通过拦截对该方法的所有调用可以很简单的破坏这种加密模式,并且很方便的反编译你感兴趣的Class文件。
做这种拦截并不困难,实际上破坏自己建立的保护模式比用工具更加迅速的。首先,我取得基于J2SDK的java.lang.ClassLoader源文件,并修改defineClass(String, byte[], int, int, ProtectionDomain)方法,在里面加入其他的类。正如下面:
...
c = defineClass0(name, b, off, len, protectionDomain);
// Intercept classes defined by the system loader and its children:
if (isAncestor (getSystemClassLoader ().getParent ()))
{
// Choose your own dump location here [use an absolute pathname]:
final File parentDir = new File ("c:/TEMP/classes/");
File dump = new File (parentDir,
name.replace ('.', File.separatorChar) + "[" +
getClass ().getName () + "@" +
Long.toHexString (System.identityHashCode (this)) + "].class");
dump.getParentFile ().mkdirs ();
FileOutputStream out = null;
try
{
out = new FileOutputStream (dump);
out.write (b, off, len);
}
catch (IOException ioe)
{
ioe.printStackTrace (System.out);
}
finally
{
if (out != null) try { out.close (); } catch (Exception ignore) {}
}
}
...
注意if里的语句可以过滤系统类加载器及其子类加载器,同样在defineClass()方法可以正常工作的情况下才能载入类。很难以相信不只有一个类加载器实例加载一个类,可通过在文件名堆里面加入类加载器标志我还是最终把这一问题给解决了。:-)
最后一步是用包含java.lang.ClassLoader类的可执行文件临时替换由JRE使用的文件rt.jar,你也可以使用-Xbootclasspath/p选项。
我再一次运行加密的程序,并恢复了所有的未加密的文件,这么说可以很容易的把.class文件正确的反编译。我先声明我并没有用EncryptedClassLoader类的内部机制来完成此壮举的。
在这里注意一点,假如我没去使用一个系统类,我可以使用别的方法,比如自定义一个JVMPI代理来处理JVMPI_EVENT_CLASS_LOAD_HOOK事件。
学习小结:
我希望你能对本文有所兴趣,你必须认识到得很重要的一点是在购买市面上任何反编译工具前要三思而行,除非JVM体系结构进行改革以支持class字节码在本地能进行译码转换,你才会更好的从传统的困惑中走出来,上演一场字节码的改革浪潮!
当然也有其他的更有效的方法:对类加载进行调试。尽可能地得到类加载的轨迹是很有价值的,特别是在类加载时你去捕获异常情况下使用。因此,JAVA的诞生可能纯粹是为了开源项目,当然,其他一些体系结构(如:。NET)也正在倾向于反编译。目前我就说说这种思想了.
分享到:
相关推荐
本文对Java字节码文件的安全性进行了深入的分析,并综述了现有的保护方法及其优缺点。根据分析结果,提出了使用ClassLoader类文件加密算法的改进方法,以提高Java字节码文件的安全性。 Java字节码文件面临的安全...
总的来说,Java字节码反编译工具是Java开发者的重要辅助工具,它们可以帮助我们深入理解运行时环境下的代码行为,提高开发和调试效率。JD-GUI和JD-Eclipse作为其中的代表,提供了方便快捷的反编译体验,无论是在日常...
本文将通过一个具体的Java代码示例来解析如何在Java中实现密码的加密与解密,以帮助读者深入理解并掌握这一关键技术。 #### 二、DES算法概述 DES(数据加密标准)是一种对称加密算法,由IBM公司开发,并于1977年被...
字节码加密器的工作原理通常是将编译后的Java字节码进行加密,当程序运行时,字节码加密器会动态解密并加载到Java虚拟机(JVM)中执行。这样做的好处是即使有人获取到了字节码文件,由于其已经被加密,无法直接通过...
**JByteMod-Beta:Java字节码编辑器** JByteMod-Beta是一款强大的Java字节码编辑器,专为开发者设计,旨在提供一个高效、...通过掌握这个工具,开发者能更深入地理解Java字节码,从而提升编程技能和解决问题的能力。
在Java中,源代码(.java文件)被JDK的javac编译器编译成字节码(.class文件),这是Java虚拟机(JVM)能够理解和执行的二进制格式。字节码是平台无关的,使得Java程序能在任何支持JVM的设备上运行。然而,字节码并...
本文将深入探讨如何使用Java实现URL加密,特别是基于Base64编码和编码转换的方式。我们将重点关注以下几个方面: 1. **Base64编码**:Base64是一种用于将任意二进制数据编码为ASCII字符的算法,使得数据可以在大...
本文将深入探讨如何在Java中使用MD5算法对密码进行加密,并对其进行解密验证。 首先,让我们理解MD5(Message-Digest Algorithm 5)是什么。MD5是一种广泛使用的哈希函数,它能够将任意长度的数据转化为固定长度的...
ProGuard是一个开源的Java字节码混淆器,它可以删除无用的类、字段和方法,同时混淆剩余的代码。此外,它还可以优化字节码以减小jar包的大小,提高运行效率。 1. ProGuard配置与使用 使用ProGuard需要编写一个配置...
本文将深入探讨Java中的加密技术,包括RSA、MD5、SHA256和SHA512等算法,以及如何通过示例(Demo)进行实际操作。 首先,让我们了解一下这些概念: 1. **RSA**:RSA是一种非对称加密算法,由Ron Rivest、Adi ...
1. **Luyten 0.5.4**: 这是一个跨平台的Java字节码查看器和编辑器,可以帮助开发者查看并修改`.class`文件。Luyten提供了图形化的界面,使得字节码的分析和修改变得更加直观。你可以通过加载`.class`文件,查看其...
jclasslib虽然不能直接帮助阅读源码,但它能辅助理解Java字节码,进而帮助我们更好地理解源码在编译后的形态。通过查看字节码,可以洞察编译器如何优化代码,这对于学习Java的内存管理和JVM工作原理非常有帮助。 ...
本知识点将深入探讨如何保护Java字节码,使其免受恶意运行环境中的静态和动态攻击。 一、静态攻击与防御策略 1. **反编译攻击**:黑客可以使用反编译工具,如JD-GUI或Procyon,将字节码还原为源代码,以便理解并...
在本文中,我们将深入探讨Java字节码加密的重要性,ByteCodeEncrypt的工作原理以及如何使用它来增强应用程序的安全性。 首先,了解Java字节码是至关重要的。Java源代码在编译后生成.class文件,这些文件包含的就是...
这样做可以增强应用程序的安全性,因为即使攻击者获取了加密的字节码,如果没有解密密钥,也无法直接理解其内容。 接下来,我们讨论加密算法。Java支持多种加密算法,包括对称加密和非对称加密。对称加密如DES...
1. **预处理阶段**:对原始的Java源码或编译后的字节码进行加密,通常使用对称加密算法,如AES。加密后的代码无法直接被Java虚拟机识别和执行。 2. **运行时解密**:当应用启动时,利用jvmti在类加载阶段介入,拦截...
本主题将深入探讨利用异或加密实现文件快速加密的技术细节,以及它在实际应用中的优缺点。 异或加密是一种简单而有效的加密算法,其基本原理是将明文数据与一个密钥进行逐位异或操作,得到的密文就是加密后的结果。...
7. **安全与隐私**:修改Dalvik字节码也可能用于提高应用的安全性,例如,通过hook敏感函数来防止恶意攻击,或者对敏感数据进行加密。 8. **性能优化**:通过对Dalvik字节码进行优化,可以减少方法调用的开销,提升...
总的来说,"C/C++与Java互通AES加密解密"是一个挑战性的任务,需要对加密算法、数据类型转换、内存管理以及跨平台通信有深入的理解。通过这个实践,开发者可以提升自己的编程技巧,并对数据安全有更深刻的认识。
综上所述,实现Java和C++之间的AES加密算法互通需要对AES算法有深入理解,同时熟悉Java和C++的相关加密库。在实际操作中,确保算法一致性、密钥管理和数据格式的一致性是成功的关键。提供的"3AES"文件可能包含了Java...