![3961e85b1e6bc95e01f0708ac2b5fd2a.png](https://img-blog.csdnimg.cn/img_convert/3961e85b1e6bc95e01f0708ac2b5fd2a.png)
进程环境 | 这里指跑在真实系统中的进程,比如iOS、Android系统中的App、Service、Daemon等。 |
指令级 | 这里指armv7、arm64、arm64e指令集。 |
函数 | 这里指汇编级C函数。 |
虚拟机 | 这里指可逐条执行指令的虚拟CPU,基于Unicorn实现。 |
// run function 'fn' on our VCPU with 'ctx'// return value is x0VCAPI long vc_run_interp(const void *fn, const vc_context_t *ctx);
// make a wrapper for function 'fn' with 'usrctx','callback'// return value is a new function pointer which will run under our VCPU// you can replace this pointer to target's function pointer// like C++-Vtable/Script-Native-Bridge// if return null, you should check errnoVCAPI const void *vc_make_callee(const void *fn, void *usrctx, fn_vc_callback_t callback);
其中vc_run_interp是指你丢一个函数给它,然后在虚拟机环境真实执行这个函数。vc_make_callee是指你丢一个函数给它,然后返回一个新的功能等价的函数给你,但是这个函数的执行是在虚拟机里面的,你可以把这个返回的函数指针拿去替换C++-Vtable,也可以拿去给fishhook当参数使用。两个函数的fn参数都是原始函数指针,下面我们详细介绍一下ctx这个参数。vc_context_t的定义如下:
// interpreter contexttypedef struct vc_context_t { // 用户自己的context,用在下述callback参数中 void *usrctx; //这里是指arm64虚拟机初始上下文,32位则为vc_armctx_t vc_arm64ctx_t regctx; // 用户丢给框架的回调函数,比如内存访问、函数调用之类的事件 fn_vc_callback_t callback; } vc_context_t;
它的作用是告诉vc_run_interp函数如何初始化虚拟机解释器。比如下面的测试代码:
// run interpretee directlyint vrun_print_message(const char *reason, const char **argv, FILE *cblogfp, fn_vc_callback_t cb) { vc_context_t ctx; memset(&ctx, 0, sizeof(ctx)); ctx.usrctx = cblogfp; // 用户自己的回调参数 ctx.callback = cb; // 用户自定义的回调函数 ctx.regctx.r[0].p = reason; // x0为arm64函数的第一个参数 ctx.regctx.r[1].p = (void *)argv; // x1为arm64函数的第二个参数 //将print_message函数以ctx的初始值跑在虚拟机里面 return (int)(long)vc_run_interp((void *)print_message, &ctx);}
我们继续看看fn_vc_callback_t这个框架回调函数的定义:
// callback prototypetypedef vc_callback_return_t (*fn_vc_callback_t)(vc_callback_args_t *args);
这个回调函数是UnicornVM虚拟机在执行到特殊的事件时调用的,以供用户自己决定如何处理,比如改写内存、函数重定向、修改系统调用参数等高级操作。回调事件定义如下:
// opcode type for callback argstypedef enum vc_optype_t { vcop_read, // memory read vcop_write, // memory write vcop_call, // function call vcop_return, // function return vcop_svc, // arm64 syscall vcop_ifetch, // interpreter fetch instruction} vc_optype_t;
它包含在
vc_callback_args_t回调参数里面,定义如下:
// callback argstypedef struct vc_callback_args_t { // your own context passed for vc_run_interp/vc_make_callee const void *usrctx; // arm64 execution context vc_arm64ctx_t *arm64ctx; // current opcode vc_optype_t op; union { // for vcop_read/vcop_write/vcop_ifetch struct { const void *src; void *dst; int byte; } rw; // for vcop_call struct { const void *callee; } call; // for vcop_return struct { const void *hitaddr; // which address hit return } ret; // for vcop_svc struct { int sysno; // syscall number } svc; } info;} vc_callback_args_t;
这里面详细记录了回调事件的具体参数,比如
vcop_read事件发生时,rw字段里面的src、dst、byte就依次说明了内存源地址、写入地址、字节大小。再比如当vcop_svc事件发生时,svc字段里面的sysno就给出了当前系统调用的调用号,然后再通过虚拟机上下文arm64ctx就可以取得系统调用的具体参数。一个具体的测试回调函数如下:
// log runtime informationstatic vc_callback_return_t interp_callback_log(vc_callback_args_t *args) { FILE *logfp = (FILE *)args->usrctx; switch (args->op) { case vcop_read: case vcop_write: { break; } case vcop_call: { fprintf(logfp, "vcop call : func %p.\n", args->info.call.callee); break; } case vcop_return: { fprintf(logfp, "vcop return : hit address %p.\n", args->info.ret.hitaddr); break; } case vcop_svc: { fprintf(logfp, "vcop syscall : syscall number %d.\n", args->info.svc.sysno); break; } default: { fprintf(logfp, "unknown vcop %d.\n", args->op); break; } } return cbret_directcall; // return cbret_recursive;}
回调函数的返回值用于指示虚拟机解释器如何作出反应,它的定义如下:
// callback return typetypedef enum vc_callback_return_t { cbret_continue, // let interp continue cbret_processed, // already processed by callback implementation cbret_recursive, // interp this function recursively cbret_directcall, // call this function directly} vc_callback_return_t;
cbret_continue是告诉解释器继续解释执行当前指令。cbret_processed是告诉解释器回调函数已经处理过了,忽略执行当前指令。cbret_recursive是指递归解释执行当前调用的函数。cbret_directcall是指切换到host环境调用当前调用的函数。针对不同架构,有不同的虚拟机上下定义,比如arm64的虚拟机上下文为:
// arm64 common register like x0 x1 ... lr sp pctypedef union vc_cmmreg_t { unsigned int w; unsigned long long x; int sw; long long sx; const void *p; const char *s; unsigned int ws[2]; int sws[2];} vc_cmmreg_t;// arm64 neon register like s0 d0 q0 ...typedef union vc_neonreg_t { unsigned int i[4]; unsigned long long l[2]; int si[4]; long long sl[2];} vc_neonreg_t;// special register wrapper#define ARM64_FP(c) (c).r[29]#define ARM64_LR(c) (c).r[30]#define ARM64_SP(c) (c).r[31]#define ARM64_PC(c) (c).pc// arm64 execution contexttypedef struct vc_arm64ctx_t { vc_cmmreg_t r[32]; // 0-28,29-fp,30-lr,31-sp vc_neonreg_t v[32]; vc_cmmreg_t pc;} vc_arm64ctx_t;
armv7的上下文定义如下:
// arm common register like r0 r1 ... lr sp pctypedef union vc_cmmreg_t { unsigned int w; int sw; const void *p; const char *s;} vc_cmmreg_t;// arm neon register like s0 d0 ...typedef union vc_neonreg_t { unsigned int i[2]; unsigned long long l; int si[2]; long long sl; float f[2]; double d;} vc_neonreg_t;// special register wrapper#define ARM_FP(c) (c).r[12]#define ARM_SP(c) (c).r[13]#define ARM_LR(c) (c).r[14]#define ARM_PC(c) (c).pc// arm execution contexttypedef struct vc_armctx_t { vc_cmmreg_t r[16]; // 0-12, 13-sp, 14-lr, 15-reserved vc_neonreg_t v[32]; vc_cmmreg_t pc;} vc_armctx_t;
完整的测试代码请参见https://gitee.com/geekneo/VirtualCode里面UnicornVM目录下面的sample。UnicornVM既简单又不简单,你学会了吗?如果你有任何建议,欢迎通过私信、头条、抖音、视频号、微信的‘刘柏江’、‘刘柏江VM’、‘GeekNeo’等渠道提交给我们,谢谢各位朋友的支持,一如既往的么么哒。