原创

浅析Java程序的破解与防护

1、破解的时机

java程序的执行过程

  • 源码阶段(可获取到源码)
  • 静态修改class文件
  • class加载到内存

2、源码阶段

  • 开源项目,可以直接获取源码
  • 简单的程序,依赖比较少,且未加密混淆。可以通过反编译工具直接获取source。读懂逻辑后直接修改。重新打包。

这里有个问题是对于加密混淆或依赖比较多的程序,一来是反编译后逻辑晦涩难懂,而且重新打包成class的难度比较大。

image-20210930135500206

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

notepad++修改

重新打开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代码。

  缺点:有人认为加密锁用户使用上不太方便,且每个安装需要附带一个。

正文到此结束
本文目录