浅析Java程序的破解与防护
1、破解的时机
- 源码阶段(可获取到源码)
- 静态修改class文件
- class加载到内存
2、源码阶段
- 开源项目,可以直接获取源码
- 简单的程序,依赖比较少,且未加密混淆。可以通过反编译工具直接获取source。读懂逻辑后直接修改。重新打包。
这里有个问题是对于加密混淆或依赖比较多的程序,一来是反编译后逻辑晦涩难懂,而且重新打包成class的难度比较大。
3、静态修改
前提是读懂了程序的大致逻辑,知道自己关注的逻辑所以的class以及处理方法。然后借助三方工具。修改单个class.完毕后将修改后的class 借助压缩软件重新打包到jar中。
3.1、16进制修改class
- 获取关键逻辑所在的class
- 借助三方工具 jd-gui和jclasslib来分析关键代码。
- 如果控制的关键逻辑是某些ConstantPool变量决定的,那么可以借助jclasslib的工具类获取到这个变量并重新赋值就可以了;如果是内部逻辑的话,需要分析关键代码对应的jvm代码,然后转化为对应的16进制去操作。可以使用notepad++插件完成
所以关键是修改这个过期的年份,对应的method是getYear
工具的缘故,截图中的常量显示不全
0 sipush 2016
3 ireturn
sipush = 17 (0x11)
2016 = 2016 (0x07E0)
ireturn = 172 (0xac)
连起来就是
1107E0AC
目标是将2016进行修改未2099
2099 = 2099 (0x0833)
所以就是将1107E0AC 替换为 110833AC
重新打开class进行查看
启动运行看下效果
jclasslib修改的实例,需要自行引用jclasslib的jar
public static void main(String[] args) throws IOException, InvalidByteCodeException {
String filePath = "\\CensumStartupChecks.class";
FileInputStream fis = new FileInputStream(filePath);
DataInput di = new DataInputStream(fis);
ClassFile cf = new ClassFile();
cf.read(di);
CPInfo[] infos = cf.getConstantPool();
int count = infos.length;
for (int i = 0; i < count; i++) {
if (infos[i] != null) {
System.out.print(i);
System.out.print(" = ");
System.out.print(infos[i].getVerbose());
System.out.print(" = ");
System.out.println(infos[i].getTagVerbose());
//这个改成实际常量池的序号
if (i == 59) {
ConstantUtf8Info uInfo = (ConstantUtf8Info) infos[i];
uInfo.setBytes("2099".getBytes());
infos[i] = uInfo;
}
}
}
cf.setConstantPool(infos);
fis.close();
File f = new File(filePath);
ClassFileWriter.writeToFile(f, cf);
}
3.2、javassist修改class
3.2.1、pom引入
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
3.2.2、调整class的代码
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
ClassPool classPool=ClassPool.getDefault();
classPool.insertClassPath("D:\\censum-full.jar");
CtClass ctClass=classPool.get("com.jclarity.censum.CensumStartupChecks");
CtMethod ctMethod=ctClass.getDeclaredMethod("getYear");
ctMethod.setBody("return 2099;");
ctClass.writeFile("D:\\libs");
}
3.3、javaagent的方式
3.3.1、引入pom
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>7.1</version>
</dependency>
</dependencies>
<build>
<finalName>censum-crack</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>tech.chenxing.agent.AgentMain</Agent-Class>
<Premain-Class>tech.chenxing.agent.AgentMain</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.ow2.asm</pattern>
<shadedPattern>me.ya.agent.hidden.org.ow2.asm</shadedPattern>
</relocation>
<relocation>
<pattern>org.objectweb.asm</pattern>
<shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
3.3.2、编写javaagent
public class AgentMain {
/**
* Premain
*
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("enter agent");
inst.addTransformer(new MyClassFileTransformer(), true);
}
/** AdviceAdapter */
public static class MyMethodVisitor extends AdviceAdapter {
MyMethodVisitor(MethodVisitor mv, int access, String name, String desc) {
super(Opcodes.ASM7, mv, access, name, desc);
}
@Override
protected void onMethodEnter() {
System.out.println("命中字段");
mv.visitFieldInsn(
GETSTATIC,
"com/jclarity/censum/CanLoadState",
"SUCCESS",
"Lcom/jclarity/censum/CanLoadState;");
mv.visitInsn(ARETURN);
}
}
/** ClassVisitor */
public static class MyClassVisitor extends ClassVisitor {
MyClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// 只注入 canLoadCensum 方法
String canLoadMethodName = GlobalConstant.methodName;
if (name.equals(canLoadMethodName)) {
System.out.println("命中方法");
return new MyMethodVisitor(mv, access, name, desc);
}
return mv;
}
}
/** Transformer */
public static class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classBytes)
throws IllegalClassFormatException {
String canLoadClassName = GlobalConstant.className;
if (className.equals(canLoadClassName)) {
System.out.println("命中class");
ClassReader cr = new ClassReader(classBytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
return classBytes;
}
}
}
3.3.3、执行jar
java -javaagent:agent1.jar -javaagent:agent2.jar -jar MyProgram.jar
4、java软件保护
https://cloud.tencent.com/developer/article/1543438
4.1 将java包装成exe
特点:将jar包装成可执行文件,便于使用,但对java程序没有任何保护。
不要以为生成了exe就和普通可执行文件效果一样了。这些包装成exe的程序运行时都会将jar文件释放到临时目录,很容易获取。
常用的工具有exe4j、jsmooth、NativeJ等等。jsmooth生成的exe运行时临时目录在exe所在目录中或是用户临时目录 中;exe4j生成的exe运行时临时目录在用户临时目录中;NativeJ生成的exe直接用winrar打开,然后用zip格式修复成一个jar文 件,就得到了原文件。如果只是为了使用和发布方便,不需要保护java代码,使用这些工具是很好的选择。
4.2 java混淆器
特点:使用一种或多种处理方式将class文件、java源代码进行混淆处理后生成新的class,使混淆后的代码不易被反编译,而反编译后的代码难以阅 读和理解。
这类混淆器工具很多,而且也很有成效。
缺点:虽然混淆的代码反编译后不易读懂,但对于有经验的人或是多花些时间,还是能找到或计算出你代码中隐藏的敏感内容,而且在很多应用中不是全部代码都能 混淆的,往往一些关键的库、类名、方法名、变量名等因使用要求的限制反而还不能混淆。
4.3 隔离java程序到服务端
特点:把java程序放到服务端,让用户不能访问到class文件和相关配套文件,客户端只通过接口访问。
这种方式在客户/服务模式的应用中能较好地保护java代码。
缺点是:必须是客户/服务模式,这种特点限制了此种方式的使用范围;客户端因为逻辑的暴露始终是较为薄弱的环节,所以访问接口时一般都需要安全性认证。
4.4 java加密保护
特点:自定义ClassLoader,将class文件和相关文件加密,运行时由此ClassLoader解密相关文件并装载类,要起到保护作用必须自定 义本地代码执行器将自定义ClassLoader和加密解密的相关类和配套文件也保护起来。
此种方式能很有效地保护java代码。
缺点:可以通过替换JRE包中与类装载相关的java类或虚拟机动态库截获java字节码。
jar2exe属于这类工具。
4.5 提前编译技术(AOT)
特点:将java代码静态编译成本地机器码,脱离通用JRE。
此种方式能够非常有效地保护java代码,且程序启动比通用JVM快一点。
具有代表性的是GNU的gcj,可以做到对java代码完全提前编译,但gcj存在诸多局限性,如:对JRE 5不能完整支持、不支持JRE 6及以后的版本。
由于java平台的复杂性,做到能及时支持最新java版本和JRE的完全提前编译是非常困难的,所以这类工具往往采取灵活方式,该用即时编译的地方还是 要用,成为提前编译和即时编译的混合体。
缺点:由于与通用JRE的差异和java运用中的复杂性,并非java程序中的所有jar都能得到完全的保护;只能使用此种工具提供的一个运行环境,如果 工具更新滞后或你需要特定版本的JRE,有可能得不到此种工具的支持。
Excelsior JET属于这类工具。
4.6 使用jni方式保护
特点:将敏感的方法和数据通过jni方式处理。
此种方式和“隔离java程序到服务端”有些类似,可以看作把需要保护的代码和数据“隔离”到动态库中,不同的是可以在单机程序中运用。
缺点和上述“隔离java程序到服务端”类似。
4.7 不脱离JRE的综合方式保护
特点:非提前编译,不脱离JRE,采用多种软保护方式,从多方面防止java程序被窃取。
此种方式由于采取了多种保护措施,比如自定义执行器和装载器、加密、JNI、安全性检测、生成可执行文件等等,使保护力度大大增强,同样能够非常有效地保 护java代码。
缺点:由于jar文件存在方式的改变和java运用中的复杂性,并非java程序中的所有jar都能得到完全的保护;很有可能并不支持所有的JRE版本。
JXMaker属于此类工具。
4.8 用加密锁硬件保护
特点:使用与硬件相关的专用程序将java虚拟机启动程序加壳,将虚拟机配套文件和java程序加密,启动的是加壳程序,由加壳程序建立一个与硬件相关的 受保护的运行环境,为了加强安全性可以和加密锁内植入的程序互动。
此种方式与以上“不脱离JRE的综合方式保护”相似,只是使用了专用硬件设备,也能很好地保护java代码。
缺点:有人认为加密锁用户使用上不太方便,且每个安装需要附带一个。