You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C语言实现Jar二进制转编码字符串并还原,复刻get-pip.py逻辑?

嘿,这个需求和get-pip.py的核心思路完全一致——把二进制资源打包成可嵌入的文本格式,程序运行时再解码还原。用C语言实现的话,我建议分两个阶段来做:一个是离线工具负责把Jar转成C能直接用的字符串,另一个是运行时程序负责解码还原。下面是具体方案:

核心思路:二进制转可打印文本编码

Jar文件是二进制格式,里面包含大量不可打印字符(比如0x00、控制字符等),直接存成C字符串会导致编译错误或数据丢失。所以必须先把二进制数据转换成纯可打印的编码格式,常用的有两种:

  • 十六进制:1字节转2个字符,实现简单但体积翻倍
  • Base64:3字节转4个字符,体积仅增加约33%,更高效,是get-pip.py这类工具的首选

我们优先选用Base64编码方案。

第一阶段:编写离线工具,将Jar转为Base64并生成C头文件

这个工具的作用是读取Jar的二进制数据,编码成Base64字符串,然后输出为C头文件,方便嵌入到主程序中。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

// Base64编码表
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// 计算Base64编码后的长度
size_t base64_encode_len(size_t data_len) {
    return ((data_len + 2) / 3) * 4;
}

// 执行Base64编码
void base64_encode(const unsigned char *data, size_t data_len, char *out) {
    size_t i, j;
    for (i = 0, j = 0; i < data_len; i += 3) {
        uint32_t triple = (data[i] << 16) | 
                          ((i+1 < data_len) ? (data[i+1] << 8) : 0) | 
                          ((i+2 < data_len) ? data[i+2] : 0);
        out[j++] = base64_table[(triple >> 18) & 0x3F];
        out[j++] = base64_table[(triple >> 12) & 0x3F];
        out[j++] = (i+1 < data_len) ? base64_table[(triple >> 6) & 0x3F] : '=';
        out[j++] = (i+2 < data_len) ? base64_table[triple & 0x3F] : '=';
    }
    out[j] = '\0';
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "用法: %s <输入Jar文件> <输出头文件>\n", argv[0]);
        return 1;
    }

    // 读取Jar文件
    FILE *jar_fp = fopen(argv[1], "rb");
    if (!jar_fp) {
        perror("无法打开Jar文件");
        return 1;
    }
    fseek(jar_fp, 0, SEEK_END);
    size_t jar_size = ftell(jar_fp);
    fseek(jar_fp, 0, SEEK_SET);
    unsigned char *jar_data = malloc(jar_size);
    if (!jar_data) {
        perror("内存分配失败");
        fclose(jar_fp);
        return 1;
    }
    fread(jar_data, 1, jar_size, jar_fp);
    fclose(jar_fp);

    // 编码成Base64
    size_t b64_len = base64_encode_len(jar_size);
    char *b64_str = malloc(b64_len + 1);
    if (!b64_str) {
        perror("内存分配失败");
        free(jar_data);
        return 1;
    }
    base64_encode(jar_data, jar_size, b64_str);
    free(jar_data);

    // 生成C头文件(分段写入避免单行过长)
    FILE *hdr_fp = fopen(argv[2], "w");
    if (!hdr_fp) {
        perror("无法创建头文件");
        free(b64_str);
        return 1;
    }
    fprintf(hdr_fp, "#ifndef JAR_EMBEDDED_H\n");
    fprintf(hdr_fp, "#define JAR_EMBEDDED_H\n\n");
    fprintf(hdr_fp, "static const char *embedded_jar_base64 = \"");
    
    size_t line_len = 76; // 符合Base64换行规范,也避免编译器报错
    for (size_t k = 0; k < b64_len; k += line_len) {
        fprintf(hdr_fp, "\n\"");
        fwrite(b64_str + k, 1, (k + line_len < b64_len) ? line_len : b64_len - k, hdr_fp);
        fprintf(hdr_fp, "\"");
    }
    fprintf(hdr_fp, ";\n");
    fprintf(hdr_fp, "static const size_t embedded_jar_size = %zu;\n", jar_size);
    fprintf(hdr_fp, "#endif\n");
    
    fclose(hdr_fp);
    free(b64_str);

    printf("已成功生成头文件: %s\n", argv[2]);
    return 0;
}

这个工具会生成一个包含两个静态变量的头文件:

  • embedded_jar_base64:编码后的Base64字符串
  • embedded_jar_size:原始Jar文件的字节大小(解码时用来分配内存)
第二阶段:编写主程序,解码Base64并还原Jar文件

主程序需要包含上面生成的头文件,将嵌入的Base64字符串解码回二进制数据,再写入新的Jar文件。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "jar_embedded.h"

// Base64解码表,快速映射字符到索引
static const int base64_decode_table[] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
    -1,  0,  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, -1, -1, -1, -1, -1,
    -1, 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, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

// 执行Base64解码,返回0表示成功,-1表示失败
int base64_decode(const char *b64_str, unsigned char *out, size_t out_size) {
    size_t i = 0, j = 0;
    uint32_t triple;
    while (b64_str[i] != '\0') {
        // 跳过换行、空格等无效字符
        while (b64_str[i] != '\0' && base64_decode_table[(unsigned char)b64_str[i]] == -1) i++;
        if (b64_str[i] == '\0') break;
        
        triple = (uint32_t)base64_decode_table[(unsigned char)b64_str[i++]] << 18;
        while (b64_str[i] != '\0' && base64_decode_table[(unsigned char)b64_str[i]] == -1) i++;
        if (b64_str[i] == '\0') return -1;
        triple |= (uint32_t)base64_decode_table[(unsigned char)b64_str[i++]] << 12;
        
        while (b64_str[i] != '\0' && base64_decode_table[(unsigned char)b64_str[i]] == -1) i++;
        if (b64_str[i] == '=' || b64_str[i] == '\0') {
            out[j++] = (triple >> 16) & 0xFF;
            break;
        }
        triple |= (uint32_t)base64_decode_table[(unsigned char)b64_str[i++]] << 6;
        
        while (b64_str[i] != '\0' && base64_decode_table[(unsigned char)b64_str[i]] == -1) i++;
        if (b64_str[i] == '=' || b64_str[i] == '\0') {
            out[j++] = (triple >> 16) & 0xFF;
            out[j++] = (triple >> 8) & 0xFF;
            break;
        }
        triple |= (uint32_t)base64_decode_table[(unsigned char)b64_str[i++]];
        out[j++] = (triple >> 16) & 0xFF;
        out[j++] = (triple >> 8) & 0xFF;
        out[j++] = triple & 0xFF;
    }
    return (j == out_size) ? 0 : -1;
}

int main(int argc, char *argv[]) {
    const char *output_jar = argc > 1 ? argv[1] : "restored.jar";

    // 分配内存存储解码后的二进制数据
    unsigned char *jar_data = malloc(embedded_jar_size);
    if (!jar_data) {
        perror("内存分配失败");
        return 1;
    }

    // 解码Base64字符串
    if (base64_decode(embedded_jar_base64, jar_data, embedded_jar_size) != 0) {
        fprintf(stderr, "Base64解码失败,数据可能损坏\n");
        free(jar_data);
        return 1;
    }

    // 写入Jar文件
    FILE *jar_fp = fopen(output_jar, "wb");
    if (!jar_fp) {
        perror("无法创建输出Jar文件");
        free(jar_data);
        return 1;
    }
    fwrite(jar_data, 1, embedded_jar_size, jar_fp);
    fclose(jar_fp);
    free(jar_data);

    printf("Jar文件已成功还原到: %s\n", output_jar);
    return 0;
}
额外优化建议
  • 压缩Jar文件:转换前用jar -cMf重新打包Jar(Jar本身是zip格式),减少嵌入字符串的体积
  • 使用系统Base64库:如果目标系统有现成的Base64库(比如Linux的libcrypto),可以直接调用EVP_Encode*/EVP_Decode*函数,避免自己实现的潜在bug
  • 添加完整性校验:在头文件中加入原始Jar的MD5/SHA1哈希值,还原后校验哈希,确保数据未损坏
  • 编译优化:编译主程序时使用-Os参数优化体积,适合分发场景
编译和使用步骤
  1. 编译离线工具:gcc -o jar2c jar2c.c
  2. 生成嵌入头文件:./jar2c your.jar jar_embedded.h
  3. 编译主程序:gcc -o restore_jar restore_jar.c
  4. 运行还原Jar:./restore_jar restored.jar

内容的提问来源于stack exchange,提问作者Sam Thomas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:05:31