如何用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参数优化体积,适合分发场景
编译和使用步骤
- 编译离线工具:
gcc -o jar2c jar2c.c - 生成嵌入头文件:
./jar2c your.jar jar_embedded.h - 编译主程序:
gcc -o restore_jar restore_jar.c - 运行还原Jar:
./restore_jar restored.jar
内容的提问来源于stack exchange,提问作者Sam Thomas
相关产品推荐
相关产品推荐

