Python 构建壳来启动加密的 SpringBoot Jar 包,增加反编译难度

Python 构建壳来启动加密的 SpringBoot Jar 包

通过将 Python 打包成可执行二进制文件来启动 SpringBoot Jar,可以避免 Jar 包容易被反编译的情况。

1.构建加密 SpringBoot jar

bash 复制代码
// 需要传入三个参数:
// 第一个:jar 包加密密钥,和后面 launcher.py 里面的 self.encryption_key 相对应;
// 第二个:未加密的 jar 包;
// 第三个:已加密的 jar 包;

java -jar JarEncryptor-1.0-SNAPSHOT.jar [b9e8c40dc6ca4eb9928930344812daf4] [origin.jar] [ecrypted.jar]

JarEncryptor-1.0-SNAPSHOT.jar 源码如下。

java 复制代码
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class JarEncryptor {

    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";

    public static void main(String[] args) throws Exception {

        // # 此处是密钥,和下面构建 launcher.bin 的 Python 脚本密钥相同,可自行修改,但需要和下面保持一致
        String encryptionKey = args[0];
        String inputFile = args[1];
        String outputFile = args[2];

        // 生成密钥和 IV
        byte[] key = generateKey(encryptionKey);
        byte[] iv = generateIv(encryptionKey);

        // 加密文件
        encryptFile(inputFile, outputFile, key, iv);

        System.out.println("JAR encrypted successfully");
        System.out.println("Python launcher command:");
        System.out.printf("python launcher.py %s%n", outputFile);
    }

    private static byte[] generateKey(String seed) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(seed.getBytes());
        // 取前 32 字节作为 AES-256 密钥
        byte[] key = new byte[32];
        System.arraycopy(hash, 0, key, 0, 32);
        return key;
    }

    private static byte[] generateIv(String seed) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("MD5");
        return digest.digest(seed.getBytes());
    }

    private static void encryptFile(String inputPath, String outputPath, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

        try (FileInputStream in = new FileInputStream(inputPath);
             FileOutputStream out = new FileOutputStream(outputPath)) {

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                byte[] encrypted = cipher.update(buffer, 0, bytesRead);
                out.write(encrypted);
            }

            byte[] encrypted = cipher.doFinal();
            out.write(encrypted);
        }
    }

}
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxx.com</groupId>
    <artifactId>JarEncryptor</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <packaging>jar</packaging>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <!--生成的jar中,不要包含pom.xml和pom.properties这两个文件-->
                        <addMavenDescriptor>true</addMavenDescriptor>
                        <manifest>
                            <mainClass>com.xxx.jar.encryptor.JarEncryptor</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.构建 libhidejar.so

用来构建 linux 环境变量库,用来隐藏 java -jar 后面的真实的 jar 包路径。

bash 复制代码
# 创建 hide_jar_path.c 文件,内容如下:
vim hide_jar_path.c
c 复制代码
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int (*_main) (int, char * *, char * *);
static int pretend_main(int argc, char **argv, char **env)
{
  	char tmp[200];
		strcpy(tmp, argv[1]);
		strncpy(argv[1], "", strlen(argv[1]));
		argv[1] = tmp;

    char tmp2[200];
		strcpy(tmp2, argv[argc-1]);
		strncpy(argv[argc-1], "", strlen(argv[argc-1]));
		argv[argc-1] = tmp2;

		return _main(argc, argv, env);
}

int (*orig_start_main)(int (*main)(int, char **, char **),
    int argc,
    char **argv,
    void (*init) (void),
    void (*fini) (void),
    void (*_fini) (void),
    void (*stack_end));


int __libc_start_main(int (*main)(int, char **, char **),
    int argc, char **argv,
    void (*init)(void),
    void (*fini)(void),
    void (*_fini)(void),
    void (*stack_end))
{
		orig_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
		_main = main;
		return orig_start_main(pretend_main, argc, argv, init, fini, _fini, stack_end);
}

通过如下命令构建

bash 复制代码
# 系统没有 gcc 环境的,需要先安装 gcc 环境

gcc -shared -fPIC -o libhidejar.so hide_jar_path.c -ldl

3.构建 launcher.bin

注意,构建操作应该在和实际运行环境相同的 Linux 操作系统上进行。依赖 pycryptodomenuitka,可以通过 pip 进行安装。新建 launcher.py 脚本,内容如下。

bash 复制代码
# 安装依赖库
pip install pycryptodome
pip install nuitka

# 新建 launcher.py 脚本,输入如下内容
vim launcher.py
python 复制代码
#!/usr/bin/env python3
import argparse
import hashlib
import os
import signal
import subprocess
import sys
import tempfile

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad


class JarLauncher:

    def __init__(self):
        self.process = None
        self.temp_dir = None
        self._setup_signal_handlers()
        
        # 此处是密钥,和上面构建加密 SpringBoot Jar 相同,可自行修改,但需要和上面保持一致
        self.encryption_key = 'b9e8c40dc6ca4eb9928930344812daf4'

    def _setup_signal_handlers(self):
        """
        设置信号处理器确保资源清理
        """

        signal.signal(signal.SIGINT, self._handle_signal)
        signal.signal(signal.SIGTERM, self._handle_signal)

    def _handle_signal(self, signum, frame):
        """
        处理终止信号
        """

        print(f"\nReceived signal {signum}, terminating...")
        self.cleanup()
        sys.exit(1)

    def _get_encryption_key(self):
        """
        从安全位置获取加密密钥
        """

        # 实际使用中可以从环境变量、密钥管理服务或加密配置文件中获取
        key_seed = self.encryption_key

        # 使用 SHA-256 生成固定长度的密钥
        return hashlib.sha256(key_seed.encode()).digest()[:32]  # AES-256需要32字节

    def _get_iv(self):
        """
        获取初始化向量(IV)
        """

        # 可以与密钥一起存储或使用固定值(降低安全性但简化实现)
        iv_seed = self.encryption_key
        return hashlib.md5(iv_seed.encode()).digest()  # 16字节

    def decrypt_jar(self, encrypted_path, key, iv):
        """
        解密 JAR 文件
        """

        with open(encrypted_path, 'rb') as f:
            encrypted_data = f.read()

        cipher = AES.new(key, AES.MODE_CBC, iv)
        try:
            return unpad(cipher.decrypt(encrypted_data), AES.block_size)
        except ValueError as e:
            print(f"Decryption failed: {str(e)}")
            print("Possible causes: incorrect key/IV or corrupted file")
            sys.exit(1)

    def execute_jar(self, jar_data, java_args=None):
        """
        执行 JAR 文件
        """

        self.temp_dir = tempfile.mkdtemp(prefix='jar_launcher_')
        temp_jar_path = os.path.join(self.temp_dir, 'application.jar')

        try:
            # 写入临时文件
            with open(temp_jar_path, 'wb') as f:
                f.write(jar_data)

            # 构建 Java 命令
            cmd = ['java', '-jar']

            wrapper_jar_file_path = os.path.join(os.path.dirname(__file__), 'WrapperLauncher.jar')
            ld_reload_path = os.path.join(os.path.dirname(__file__), 'libhidejar.so')

            if java_args:
                cmd.extend(java_args.split())

            cmd.append(wrapper_jar_file_path)

            env = os.environ.copy()
            env["LD_PRELOAD"] = ld_reload_path
            
            # 该环境变量和 WrapperLauncher 中相对应,要改则需要一起改
            env["AWESOME_JAR_XXX"] = temp_jar_path

            self.process = subprocess.Popen(
                cmd,
                stdout=sys.stdout,
                stderr=sys.stderr,
                stdin=sys.stdin,
                bufsize=1,
                universal_newlines=True,
                env=env
            )

            # 等待进程结束
            return self.process.wait()

        except Exception as e:
            print(f"Failed to execute JAR: {str(e)}")
            return 1

    def cleanup(self):
        """
        清理资源
        """

        if self.process and self.process.poll() is None:
            self.process.terminate()
            try:
                self.process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                self.process.kill()

        if self.temp_dir and os.path.exists(self.temp_dir):
            try:
                import shutil
                shutil.rmtree(self.temp_dir)
            except Exception as e:
                print(f"Warning: Failed to clean temp directory: {str(e)}")


def main():
    parser = argparse.ArgumentParser(description='SpringBoot JAR Launcher')
    parser.add_argument('--jar-file', help='Path to the encrypted JAR file', default='')
    parser.add_argument('--java-args', help='Additional Java VM arguments', default='')
    args = parser.parse_args()

    launcher = JarLauncher()

    try:
        # 获取密钥
        key = launcher._get_encryption_key()
        iv = launcher._get_iv()

        # 解密 JAR
        print("Decrypting JAR file...")
        jar_file = args.jar_file
        if not jar_file:
            jar_file = os.path.join(os.path.dirname(__file__), 'application.jar')

        jar_data = launcher.decrypt_jar(jar_file, key, iv)

        # 执行 JAR
        print("Starting application...")
        exit_code = launcher.execute_jar(jar_data, args.java_args)

        sys.exit(exit_code)

    except Exception as e:
        print(f"Fatal error: {str(e)}", file=sys.stderr)
        launcher.cleanup()
        sys.exit(1)

    finally:
        launcher.cleanup()


if __name__ == '__main__':
    main()

通过如下命令进行构建。

bash 复制代码
python3 -m nuitka --standalone --onefile --output-dir=out \
    --include-data-files=[your path of jar encrypted]=application.jar \
    --include-data-files=./WrapperLauncher-1.0.0-SNAPSHOT.jar=WrapperLauncher.jar \
    --include-data-files=./libhidejar.so=libhidejar.so \
    launcher.py

构建成功后,在 out 目录里面会有 launcher.bin 启动类。

4.启动 launcher.bin

launcher.bin 是将 Springboot Jar 包含在内的二进制文件,增加逆向的难度。

bash 复制代码
./launcher.bin [--jar-file=your external encrypted jar] [--java-args="[your extra args]"]

启动完成后,是看不到解密后的 jar 路径的。

5.WrapperLauncher

WrapperLauncher 是通过环境变量获取实际的 jar 路径,通过类加载器加载实际的 springboot jar 包,增加一层寻找真实 jar 的障碍。下面是其源码和 pom.xml 配置

java 复制代码
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class WrapperLauncher {

    public static void main(String[] args) throws Exception {

      	// 该环境变量和 launcher.py 中相对应,要改则需要一起改
        String awesomeJarPath = System.getenv("AWESOME_JAR_XXX");
        URL jarUrl = new File(awesomeJarPath).toURI().toURL();
        URLClassLoader loader = new URLClassLoader(new URL[]{jarUrl});
        Class<?> mainClass = loader.loadClass("org.springframework.boot.loader.JarLauncher");
        Method mainMethod = mainClass.getMethod("main", String[].class);
        mainMethod.invoke(null, (Object) args);
    }

}
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxx.com</groupId>
    <artifactId>WrapperLauncher</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <packaging>jar</packaging>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <addMavenDescriptor>true</addMavenDescriptor>
                        <manifest>
                            <mainClass>com.xxx.wrapper.launcher.WrapperLauncher</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
相关推荐
张朝阳的博客29 分钟前
哈夫曼树Python实现
开发语言·python
狮子也疯狂1 小时前
基于Spring Boot的宿舍管理系统设计与实现
java·spring boot·后端
里探1 小时前
FastAPI的初步学习(Django用户过来的)
python·django·fastapi
程序员一诺python2 小时前
【Django开发】django美多商城项目完整开发4.0第2篇:项目准备,配置【附代码文档】
后端·python·django·框架
面朝大海,春不暖,花不开2 小时前
Java服务提供者模式实现指南
java·开发语言·python
mit6.8242 小时前
[Data Pipeline] MinIO存储(数据湖) | 数据层 Bronze/Silver/Gold
数据库·python
love530love2 小时前
Python 开发环境全栈隔离架构:从 Anaconda 到 PyCharm 的四级防护体系
运维·ide·人工智能·windows·python·架构·pycharm
烧烤店小蚂蚁2 小时前
打卡Day55
python
刘瑞瑞rr3 小时前
python画三维立体图
开发语言·python
草明4 小时前
Python pip 以及 包的升级
windows·python·pip