当前主流的 Java 代码保护技术有: 文件级加密 方法级加密 控制流混淆 名称(类名/方法名等)混淆 然而这些保护技术在安全性上各自存在一些不足(如对抗内存 Dump、反混淆工具等)。本文介绍一种新的保护方案——虚拟机保护,它是一种安全性极高的保护方式,通过将 Java JVM 字节码转换为自定义的虚拟机字节码,再到自定义的虚拟机解释器中解释执行,从根本上杜绝原始字节码的暴露风险。 JVM 字节码简介 Java 源代码经过编译后,class 文件中包含了 Java 字节码,通过解析 class 文件,可以得到每个方法的 JVM 字节码。 Class 文件格式 对字节码反汇编,可以得到对应的 JVM 指令: Main.java (源文件) Main.smali (反汇编后的JVM指令) 将 Java 方法 Native 化 以上文中的 main 方法为例,可以将 JVM指令转换为 C 代码: 原始的 Java代码中的 main 方法可以等价替换为: 理论上可以将要保护的所有方法,都转换为 native 方法,但会带来以下问题: 转换过程中依赖 C/C++ 编译器生成各平台的动态库。 转换的 method 多了 C/C++ 的动态库会很大。 虚拟机保护实现思路 虚拟机保护,其基本原理是将 JVM 字节码翻译为自定义的中间指令(VM Code),然后在 Native 层的动态库中解释执行 VM Code。 JVM 转 VM Code 上文中 main 的方法体,可以替换为一个跳转方法,转换为以下形式: 其中,CC0000006603E7A6000AE87B8EB0D714 是一个随机的 Java Class: vm_void 是一个 native 方法,实现在 C/C++ 编写的动态库中,作为解释器的入口,其中 method_index 代表这个方法的 ID,将 method_index 与原始的方法字节码可以做映射保存。 解释执行原理 上面的示例中,将 main 方法的真实代码抽走,跳转到 C/C++ 编写的 vm_void 函数,vm_void 的代码逻辑大致如下(以伪代码表示): 上述伪代码中 vm_code,是将方法体中的 JVM 字节码转换后的自定义虚拟机字节码,虚拟机字节码可以包含需要的各种操作,包括算术运算、逻辑运算、方法调用、条件跳转等,从而解释执行被保护的方法。 安全性 上面所述的 vm_code,对应了一种自定义的编码规则,想要还原被保护的方法,需要逆向分析整个 C/C++ 实现的解释器的逻辑,分析 vm_code 编码的 opcode 和 jvm 字节码的对应关系。 而 vm_code 在保护时可以进行随机化,而且 C/C++ 解释器的解码逻辑也可以通过 Virbox Protector 二进制保护技术再次保护,使得逆向分析难上加难。 从以上技术原理可以看出,Java 虚拟机保护方案,安全强度远高于控制流混淆的保护方式,特别适用于高安全性需求的保护场景。 如需体验测试 Java 虚拟机保护,可在 Virbox Protector 官网下载保护工具免费试用。 END 👇扫码进群,一起畅聊技术,共享前沿资讯! 👇点击关注,🌟星标深盾,了解更多精彩内容!public class Main {
public static void main(String[] args) {
System.out.println("Hello, world");
}
}
.method public static main([Ljava/lang/String;)V
.max stack 2
.max locals 1
.local 0 "args" [Ljava/lang/String;
.line 7
getstatic java/lang/System out Ljava/io/PrintStream;
ldc "Hello, world"
invokevirtual java/io/PrintStream println (Ljava/lang/String;)V
.line 8
return
.end method
void cpp_main(JNIEnv* env, jclass clazz, jobjectArray args)
{
jclass cid = env->FindClass("java/lang/System");
jfieldID fid = env->GetStaticFieldID(cid, "out", "Ljava/io/PrintStream;");
jobject out = env->GetStaticObjectField(cid, fid);
jclass printstream = env->FindClass("java/io/PrintStream");
jmethodID println = env->GetMethodID(printstream, "println", "(Ljava/lang/String;)V");
const char* cstr = "Hello, world";
jstring str = env->NewStringUTF(cstr);
env->CallObjectMethod(out, println, str);
env->DeleteLocalRef(str);
}
public class Main {
static {
System.loadLibrary("hello"); // 加载 native动态库
}
public static void main(String[] args) {
// System.out.println("Hello, world");
// 转换为 native 调用
cpp_main();
}
public native static void cpp_main();
}
public class Main {
public static void main(String[] args) {
// 跳转方法
CC0000006603E7A6000AE87B8EB0D714.vm_void(1, new Object[]{args});
}
}
public class CC0000006603E7A6000AE87B8EB0D714 {
public static native void vm_void(int method_index, Object[] method_args);
}
void JNICALL vm_void(JNIEnv* env, jint method_index, jobjectArray args)
{
// local vars
int local_vars[local_size];
// 获取 method_index 对应的 vm code
vm_code = getMethodVmCode(method_index);
insn = vm_code;
while (insn < vm_code.size())
{
switch(insn.opcode)
{
case OP_ADD:
int a1 = get_op1(insn);
int a2 = get_op2(insn);
int index = get_dest(insn);
local_vars[index] = a1 + a2;
break;
case OP_SUB:
// do sub
case OP_INVOKE_STATIC:
// method_id = get_method_id(insn);
// args = get_method_args(insn);
// invoke static method
// ...
case OP_INVOKE_VIRTUAL:
// invoke virtual method
//...
}
insn = next_insn(insn);
}
}
Java 虚拟机代码保护原理
本文来自投稿,不代表本站立场,如若转载,请注明出处:https://typecho.firshare.cn/archives/6057.html
免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。避免网络欺诈,本站不倡导任何交易行为。如您私自与本站转载自公开互联网中的资讯内容中提及到的个人或平台产生交易,则需自行承担后果。本站在注明来源的前提下推荐原文至此,仅作为优良公众、公开信息分享阅读,不进行商业发布、发表及从事营利性活动。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。
-- 展开阅读全文 --