• Java应用的混淆、加密以及加壳


    背景

    最近用Java写了一些小工具,又涉及到一个老问题,如何更好更方便地分发?利用现成的安装程序吧,觉得太重。看到github上很多程序直接打包成一个单一的可执行文件,不超过200M,觉得这种形式不错。于是也想利用golang的特性,包装一下自己的Java命令行应用。当然,包装SpringBoot web应用也是可以的。

    前言

    自从Java诞生以来,其字节码容易被反编译的问题就为程序员所诟病。由此也诞生了不少Java混淆工具和加壳软件。

    最关键的一个问题,采用import语句的类,能否被URLClassLoader加载机制替换掉?也就是,一个jar包中,如果我将一部分class移走,这部分class采用自定制的ClassLoader加载,是否可行?如果可行,那我将这部分class通过golang写的server输出,就差不多解决了一半的被破解问题。

    我的解决方案

    经过一番实验,摸索出如下方案:

    • 将jar应用拆成两部分:一个很薄的客户端cli.jar,一个包含不想暴露class文件的jar,叫mylib.jar;CLI通过URLClassLoader去加载mylib.jar
    • 利用go:embed库,将上面两个jar文件打包到golang可执行程序里去
    • 在golang里启一个http server,作为class文件的伺服,也就是mylib.jar的服务器
    • 在同一个golang里运行java命令,执行cli.jar里的主类
    • 进一步,将mylib.jar加密,然后传递密钥给cli.jar,在cli程序里解密

    上述方案称不上绝对安全,但多少增加了些破解难度。

    实现代码

    package main
    
    import (
    	"context"
    	"embed"
    	"fmt"
    	"io"
    	"log"
    	"net/http"
    	"os"
    	"os/exec"
    	"os/signal"
    	"time"
    )
    
    //go:embed cli.jar
    //go:embed mylib.jar
    
    var f embed.FS
    
    func ClassServer(w http.ResponseWriter, req *http.Request) {
    	data, _ := f.ReadFile("mylib.jar")
    	w.Write(data)
    }
    func Check(w http.ResponseWriter, req *http.Request) {
    	w.Write([]byte("ok"))
    }
    
    func launch_java() {
    	for {
    		time.Sleep(1 * time.Second) 
    		resp, _ := http.Get("http://localhost:18899/check")
    		bytes, _ := io.ReadAll(resp.Body)
    		if string(bytes) == "ok" {
    			cmd := exec.Command("java", "-cp", ".:/tmp/_cli.jar", "com.icool.CLIMain", "http://localhost:18899/jar", "passw0rd")			
    			output, _ := cmd.CombinedOutput()
    			fmt.Println(string(output))
    			break
    		}
    	}
    
    }
    
    func main() {
    	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    	defer stop()
    
    	cli, _ := f.ReadFile("cli.jar")
    	_ = os.WriteFile("/tmp/_cli.jar", cli, 0755)
    
    	go func() {
    		http.HandleFunc("/jar", ClassServer)
    		http.HandleFunc("/check", Check)
    		err := http.ListenAndServe(":18899", nil)
    		if err != nil {
    			log.Fatal("ListenAndServe: ", err)
    		}
    	}()
    
    	go launch_java()
    
    	// Wait for interrupt signal.
    	<-ctx.Done()
    	// Restore signal, allowing "force quit".
    	stop()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    附录

    问题

    代码混淆存在的问题

    由于JVM字节码的高语义性,使得期极为容易被分析与阅读,使用动态调试的方式可以很容易分析出其运行逻辑,而动态调试工具的编写并不是一件十分复杂的事情,因此混淆并不是一种可靠的保护方案。

    Java类文件加密存在的问题

    由于JVM附加机制的存在,所有未脱离普通JVM运行的所谓加密代码,都可以使用附加工具轻松读取,因此这是一种最无效的保护方案。
    普通的JVM都带有附着自制,用户可以使用jhsdb这类工具,附着到JVM进程,对其内存数据进行查看和分析,并且这些内存数据还是按照源文件中的数据结构被妥善组织好的,这也可以理解为JVM自带的一种后门机制。下面这篇文章介绍了如何使用JVM附着机制读取并保存内存中的类文件信息。https://protector4j.com/articles/cracking-encrypted-java-applications-jhsdb/

    除了可以使用JDK自带的jhsdb工具之外,还可以使用阿里巴巴的Arthas对运行中的Java进程进行分析。

    虚拟化保护存在的问题

    虚拟化保护是强度最高的一种代码保护方式,但是由于期对性能的严重影响,因此无法应用到程序中的全部代码,而只能保护关键代码,其他代码仍然有暴露的风险,而以其他部分代码来切入口,就可以获取到虚拟化部分代码的功能信息。

    AOT编译存在的问题

    AOT编译配置难度大,编译难度大,编译失败概率高,即使编译成功,代码逻辑也仅是由原来的字节码表示转换为机器代码表示,其本身的运行逻辑仍然存在,并没有进行特别的保护,如果能够了解其本身的编译与运行机制,仍然能够逆向还原出可读性的代码。

    Java应用的打包

    • exe4j
    • launch4j
    • JSmooth: 已经过时了
    • jpackage:JDK自带的打包工具
    • Installer工具:Inno Setup、NSIS(https://sourceforge.net/projects/nsis/)

    混淆器

    代码混淆是最早应用于Java代码保护的方案,也是一个最直接的方案。代码混淆通常有下面四种方法:

    • 包名、类名、变量名转换
    • 控制结构改变,如控制流平坦化、添加不可变谓词等
    • 字符串混淆或加密
    • 添加无用代码

    代码混淆可以大幅降低反编译代码的可读性,提升静态分析的难度,但是无论如何进行代码混淆,程序的运行逻辑是不会改变的。
    JVM字节码上是一种语义很清晰明确,且极为阅读的中间代码,对于被混淆的class文件,即使无法还原成可读的Java源代码,仍然可以在字节码层面进行分析,由于Java字节码的高语义性,这个过程其实还是比较容易的。

    下面是一些常见的开源的和商业的混淆工具:

    • ProGuard is a popular open-source GPL-licenced bytecode optimizer and file shrinker for Java and Kotlin. It claims to make these applications up to 90% smaller and 20% faster. It also provides some minimal obfuscation by renaming classes, fields and methods. Android Studio uses ProGuard automatically. ProGuard,一款shrinker(压缩:检测和删除没有使用的类,字段,方法和属性), optimizer(优化:对字节码进行优化,并且移除无用指令), obfuscator(混淆:使用a,b,c等无意义的名称,对类,字段和方法进行重命名), and preverifier(审核:在Java平台上对处理后的代码进行预检)工具。缩短进程检测并删去未运用的类、字段、办法和特点。优化器 进程优化字节码并删去未运用的指令。混杂进程运用简短无意义的称号重命名剩余的类、字段和办法。最后的预验证进程将预验证信息增加到类中,这是 Java Micro Edition 和 Java 6 及更高版别所必需的。
    • yGuard is another commonly used open-source obfuscator.
    • ZKM(Zelix KlassMaster) is a full featured commercial Java obfuscator. It shrinks and obfuscates both code and string constants.
    • Allatori is a commercial second generation Java obfuscator. 第二代Java混淆器。所谓第二代混淆器,不仅仅能进行字段混淆,还能实现流混淆。
    • DashO Java and Android Obfuscator is a commercial second generation Java obfuscator. DashO-Pro是第三代的Java混淆器(obfuscator)、压缩机(compactor)、优化工具和水印工具(watermarker)。它能有效保护和防止Java程序被反编译和篡改,是Java代码保护的理想选择。DashO-Pro除了为Java代码提供领先的代码保护外,它还将应用程序的大小缩减到原文件的70%。如果您正在找寻为您的Java程序提供反编译保护、提高运行速度和减少程序体积的办法,那么我们推荐您使用DashO。DashO是一个Java和Android的混用程序,它提供企业级应用的加固和屏蔽,大大降低了知识产权盗窃、数据盗窃、盗版和篡改的风险。分层混淆,加密,水印,自动失效,反调试,反篡改,反仿真器,反挂钩,反根设备解决方案,为世界各地的应用程序提供保护。
    • Stringer Java Obfuscation Toolkit is a commercial Java obfuscator supporting up to Java 13.

    There is an easy-to-read introductory article with extra links on bytecode obfuscation on the OWASP Foundation’s website. Another good introductory article on obfuscation techniques is on the DashO website.

    名称License地址
    yGuardLGPLhttp://www.yworks.com/products/yguard
    ProGuardGPLv2https://www.guardsquare.com/en/proguard
    Facebook ProGuard分支GPLv2https://github.com/facebook/proguard
    DashOCommercialhttps://www.preemptive.com/products/dasho
    AllatoriCommercialhttp://www.allatori.com
    StringerCommercialhttps://jfxstore.com
    Java AntidecompilerCommercialhttp://www.bisguard.com/help/java/
    Zelix KlassMasterCommercialhttp://www.zelix.com

    类加载与类加密

    先来了解一下Java类加载器的基本常识。三种调用会导致JVM加载一个类: new一个对象、Class.forName()、classLoader.loadClass(),而在文件头import语句只是声明,不会导致类加载。

    Bootstrap Class Loader

    Bootstrap class loader serves as the parent of all the other ClassLoader instances. This bootstrap class loader is part of the core JVM and is written in native code. 不同平台有不同的实现。

    It’s mainly responsible for loading JDK internal classes, typically rt.jar and other core libraries located in the $JAVA_HOME/jre/lib directory.

    Extension Class Loader

    The extension class loader is a child of the bootstrap class loader, and takes care of loading the extensions of the standard core Java classes so that they’re available to all applications running on the platform.

    The extension class loader loads from the JDK extensions directory, usually the $JAVA_HOME/lib/ext directory, or any other directory mentioned in the java.ext.dirs system property.

    System Class Loader

    The system or application class loader, on the other hand, takes care of loading all the application level classes into the JVM. It loads files found in the classpath environment variable, -classpath, or -cp command line option. It’s also a child of the extensions class loader.

    自定义ClassLoader

    java -Djava.system.class.loader
    =com.test.YourCustomClassLoader com.test.YourMainClass

    protector4j

    Protector4J可以通过加密类来保护您的java源代码,它通过修改JVM创建了一个自定义的本地ClassLoader。Java类由AES加密,并在本地ClassLoader中解密。并且它还引入了一些机制来提高破解的难度。

    加密您的代码可以保护您的知识产权,并大大提高您的应用程序的安全性。它使得IP盗窃、代码篡改和安全漏洞的发现涉及到昂贵的逆向工程努力,而实际上任何人都可以下载并运行一个免费的Java反编译器。

    Protector4J也可以帮助您为Windows,Linux,macOS创建您的Java App的可执行包装器。

    VLINX Protector4J is a tool to prevent Java applications from decompilation. Protector4J provides a custom native ClassLoader by modifying the JVM. The Java classes are encrypted by AES and decrypted in the native ClassLoader.

    几个特点:

    • 私有压缩文档格式: JARX
    • 自定义JRE仅支持加载JARX文件
    • 禁用JVM的远程附加机制
    • 二进制代码级别的保护

    JARX文件是protector4j专有存档文件格式,它使用与Zip相同的Deflate压缩算法,并使用AES加密算法来加密数据。
    JARX文件的结构与所有存档文件类型相似,由条目组成,这些条目以我们的专有方式组织,条目的名称和内容使用AES算法进行加密。
    由于JARX文件格式并未公开,且条目的内容和名称已加密,且没有工具可以直接解压和JARX文件,因此使用JARX文件不仅可以保护您的类文件的内容,还可以保护整个JAR文件的结构,即外界甚至无法获取您的类的名称,这将使其更难以破解。

    加壳

    https://gitee.com/chejiangyi/jar-protect

    在这里插入图片描述

    在这里插入图片描述

    采用Golang打包Java程序

    Golang
    binary-go就是其中一个合适的选择

    go get -u github.com/samuelngs/binary-go

    安装完之后,我们执行

    binary -dir ./[静态文件位置] -out binary

    就会产生出许多的go文件,默认它是以20M为一个进行分拆的。

    package main
    
    import (
        _ "embed"
        "fmt"
        "os"
        "os/exec"
    )
    
    //go:embed binary
    var f []byte
    
    func main() {
        _ = os.WriteFile("foobar", f, 0755)
        out, _ := exec.Command("./foobar").Output()
        fmt.Printf("Output: %s\n", out)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    cmd := exec.Command("java", "-jar", "Astro.jar", "1924 12 12 23 23 23 74.34 34.67")
    fmt.Println(cmd.Start())
    
    • 1
    • 2

    xjar

    xjar的原理是将jar包加密,然后执行的时候再加密,而密钥存放在外部的go可执行文件中。加密和解密都是由java程序完成。

    参考链接

    • https://github.com/segator/jbinary
    • go-jdk:Run JVM-based code in Go efficiently
    • https://github.com/core-lib/xjar
    • https://github.com/lqs1848/AllatoriCrack.git
    • https://yworks.github.io/yGuard/
    • https://protector4j.com/
    • https://blog.csdn.net/weixin_35569158/article/details/114567793
  • 相关阅读:
    高效搜索采集工具,让营销人员更轻松
    Worthington公司氨基酸氧化酶,L-的特异性分析
    vue前端页面实现markdown编辑
    一文彻底搞懂ZAB算法,看这篇就够了!!!
    第四章:Spring七大核心模块Bean、Core、Context
    JS逆向实战23——某市wss URL加密+请求头+ws收发
    nodejs+vue活鲜物流监控系统elementui
    python中pickle向redis中存储数据
    Angular组件间传值有哪几种方法?
    html拖动滚动
  • 原文地址:https://blog.csdn.net/jgku/article/details/133802927