对于Unity3D源码的保护前面也讨论过了一种自定义格式的方式(Unity3d保护level1)。现在继续分享一下一种自己公司保护用的方式--自定义opcode,希望对做游戏安全保护的童鞋能起到一定的启发作用。
首先需要对Unity3D的jit机制行进初步的了解,具体的可以移步gslab(Unity3D jit机制分析)。在对mono的jit编译过程有清晰的认识后,便可进行下面的自定义opcode的操作了。
这里先简单的理一遍Unity3D jit的编译过程:
// mono/mini/driver.c 文件中
int mono_main(int argc, char* argv[])
->
main_thread_handler(gpointer user_data) // 函数位于driver.c文件中
->
mono_jit_exec(MonoDomain *domain, MonoAssembly *assembly, int argc, char *argv[]) // 函数位于driver.c文件中
->
mono_runtime_run_main(MonoMethod *method, int argc, char* argv[]) // 函数位于mono\metadata\object.c中
->
mono_runtime_exec_main(MonoMethod *method, MonoArray *args, MonoObject **exc) // 函数位于 mono\metadata\object.c中
->
mono_runtime_invoke(MonoMethod *method, void *obj, void *params, MonoObject **exc) // 函数位于 mono\metadata\object.c中
->
result = default_mono_runtime_invoke (method, obj, params, exc); // 这里进行default_ mono_runtime_invoke()的调用
// 实际调用的是位于mini.c中的mono_jit_runtime_invoke()
mono_jit_runtime_invoke(MOnoMehtod *method, void *obj, void **params, MonoObject **exc)
->
mono_jit_compile_method_with_opt(method, default_opt, &jit_ex)
->
mono_jit_compile_method_inner (method, target_domain, opt, ex);
->
mini_method_compile(method, opt, target_domain, TRUE, FALSE, 0); // 进行实际的编译
在实际的编译中调用了一个关键的函数mono_method_to_ir(位于mono\mini\method-to-ir.c文件中),这里谈论的opcodde自定义,就是从这个函数入手的。
/*
* mono_method_to_ir:
*
* Translate the .net IL into linear IR.
*/
int
mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_bblock, MonoBasicBlock *end_bblock,
MonoInst *return_var, GList *dont_inline, MonoInst **inline_args,
guint inline_offset, gboolean is_virtual_call)
{
....
// 从这里对相应opcode进行处理
switch (*ip) {
case CEE_NOP:
if (seq_points && !sym_seq_points && sp != stack_start) {
/*
* The C# compiler uses these nops to notify the JIT that it should
* insert seq points.
*/
NEW_SEQ_POINT (cfg, ins, ip - header->code, FALSE);
MONO_ADD_INS (cfg->cbb, ins);
}
if (cfg->keep_cil_nops)
MONO_INST_NEW (cfg, ins, OP_HARD_NOP);
else
MONO_INST_NEW (cfg, ins, OP_NOP);
ip++;
MONO_ADD_INS (bblock, ins);
break;
case CEE_BREAK:
if (should_insert_brekpoint (cfg->method))
MONO_INST_NEW (cfg, ins, OP_BREAK);
else
MONO_INST_NEW (cfg, ins, OP_NOP);
ip++;
MONO_ADD_INS (bblock, ins);
break;
...
}
*ip 指向的是opcode,opcode的定义位于mono\cil\opcode.def中,形式如下所示:
OPDEF(CEE_NOP, "nop", Pop0, Push0, InlineNone, X, 1, 0xFF, 0x00, NEXT)
OPDEF(CEE_BREAK, "break", Pop0, Push0, InlineNone, X, 1, 0xFF, 0x01, ERROR)
OPDEF(CEE_LDARG_0, "ldarg.0", Pop0, Push1, InlineNone, X, 1, 0xFF, 0x02, NEXT)
OPDEF(CEE_LDARG_1, "ldarg.1", Pop0, Push1, InlineNone, X, 1, 0xFF, 0x03, NEXT)
OPDEF(CEE_LDARG_2, "ldarg.2", Pop0, Push1, InlineNone, X, 1, 0xFF, 0x04, NEXT)
OPDEF(CEE_LDARG_3, "ldarg.3", Pop0, Push1, InlineNone, X, 1, 0xFF, 0x05, NEXT)
...
其中关键的是第一位的opcode名称和倒数第二位的十六进制index,这里所做的保护是将第一位名称和index进行随机打乱,这里就需要自己实现一个映射表,并且去重新实现mono_method_to_ir函数中的 switch(*ip)里面的实际功能了。
这里只做简单的描述,需要自己动手去分析mono的源码和实现一下。在自己定义了libmono.so以后,需要加入Android so文件的保护,不然自定义的libmono.so被别人一分析很容易就能被分析出自定义的那个opcode表。同时也需要在游戏后台进行反外挂数据风控的部署,游戏的保护不仅仅在于前端客户端的保护,在后端的风控保护与前端客户端的保护相互配合才能更好的保护游戏。这些都是个人愚见,望轻喷。
[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!
最后于 2019-1-28 09:52
被nsec编辑
,原因: 代码整理