android hook 框架 ADBI 如何实现dalvik函数挂钩

前面几篇分析已经能做到注入一个so到目标进程并用so里的函数挂钩目标进程的函数,如果对这个实现不了解,请返回去阅读  android hook 框架 ADBI 简介、编译、运行  、 android hook 框架 ADBI 如何实现so注入 、android hook 框架 ADBI 如何实现so函数挂钩, so函数的挂钩只能影响native世界,没有影响到java虚拟机内部,而android绝大部分逻辑都是跑在虚拟机内部的。所以这篇接着分析 adbi 剩下的部分代码,看它如何实现挂钩dalvik虚拟机内部函数,使得目标进程执行java函数时能执行到我们注入的 dex 文件里的函数,这部分代码在 adbi 项目的 ddi 目录下。

 

技术分享

其中,dalvikhook目录存放具体的 dalvik 挂钩实现,examples 目录存放两个挂钩java函数的例子。我们使用第一个例子, smsdispatch 来分析这个过程。

 

一、 smsdispatch 例子主要是两个文件, smsdispatch.c 和  SMSDispatch.java , 前者编译成一个so,利用 android hook 框架 ADBI 如何实现so注入  的方法注入到目标进程,并执行挂钩操作。后者编译成一个dex 文件,smsdispatch.c 挂钩前会先加载这个Dex文件获取用来挂钩的函数。

void __attribute__ ((constructor)) my_init(void);

void my_init(void)
{
        log("libsmsdispatch: started\n")

        debug = 1;
        // set log function for  libbase (very important!)
        set_logfunction(my_log2);
        // set log function for libdalvikhook (very important!)
        dalvikhook_set_logfunction(my_log2);

        hook(&eph, getpid(), "libc.", "epoll_wait", my_epoll_wait, 0);
}

同前面的分析,my_init函数由于带有 __attribute__ ((constructor)) 修饰,smsdispatch.so 被目标进程加载后自动执行my_init, 该函数执行hook操作,挂钩目标进程的 libc.so 的 epoll_wait 函数。其实这里可以直接执行 dalvik 的挂钩的。

static int my_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
        int (*orig_epoll_wait)(int epfd, struct epoll_event *events, int maxevents, int timeout);
        orig_epoll_wait = (void*)eph.orig;
        // remove hook for epoll_wait
        hook_precall(&eph);

        // resolve symbols from DVM
        dexstuff_resolv_dvm(&d);

        // hook
        dalvik_hook_setup(&dpdu, "Lcom/android/internal/telephony/SMSDispatcher;", "dispatchPdus", "([[B)V", 2, my_dispatch);
        dalvik_hook(&d, &dpdu);

        // call original function
        int res = orig_epoll_wait(epfd, events, maxevents, timeout);
        return res;
}

目标进程epoll_wait被调用的时候,实际执行的是  my_epoll_wait , 这个函数与之前的例子相比,增加了 dexstuff_resolv_dvm、 dalvik_hook_setup、 dalvik_hook 三个函数,即执行了 dalvik的挂钩操作,被挂钩的是  com/android/internal/telephony/SMSDispatcher 类的  dispatchPdus 函数,挂成了 my_dispatch 函数。其实这几句可以直接放到  my_init 里,当so被注入时直接执行的。涉及到几个数据结构:

static struct dexstuff_t d;   
static struct dalvik_hook_t dpdu;
struct dalvik_hook_t
{
        char clname[256];
        char clnamep[256];
        char method_name[256];
        char method_sig[256];

        Method *method;
        int sm; // static method

        // original values, saved before patching
        int iss;
        int rss;
        int oss;
        int access_flags;
        void *insns; // dalvik code

        // native values
        int n_iss; // == n_rss
        int n_rss; // num argument (+ 1, if non-static method) 
        int n_oss; // 0
        void *native_func;

        int af; // access flags modifier

        int resolvm;

        // for the call
        jclass cls;
        jmethodID mid;

        // debug stuff
        int dump;      // call dvmDumpClass() while patching
        int debug_me;  // print debug while operating on this method
};
struct dalvik_hook_t 结构描述了一次dalvik hook操作需要记录的信息,包括被挂钩的java函数和用于挂钩的java 函数的信息。其中, Method *method; 成员指向被挂钩java函数的Method结构体,通过动态替换这个指针指向的结构体内部成员的值,可以影响该java函数(一个java函数其实就是一个 Method 结构体,包括真实执行的native 函数地址及其他描述信息)。

struct dexstuff_t
{
        void *dvm_hand;

        dvmCreateStringFromCstr_func dvmStringFromCStr_fnPtr;
        dvmGetSystemClassLoader_func dvmGetSystemClassLoader_fnPtr;
        dvmThreadSelf_func dvmThreadSelf_fnPtr;

        dvmIsClassInitialized_func dvmIsClassInitialized_fnPtr;
        dvmInitClass_func dvmInitClass_fnPtr;
        dvmFindVirtualMethodHierByDescriptor_func dvmFindVirtualMethodHierByDescriptor_fnPtr;
        dvmFindDirectMethodByDescriptor_func dvmFindDirectMethodByDescriptor_fnPtr;
        dvmIsStaticMethod_func dvmIsStaticMethod_fnPtr;
        dvmAllocObject_func dvmAllocObject_fnPtr;
        dvmCallMethodV_func dvmCallMethodV_fnPtr;
        dvmCallMethodA_func dvmCallMethodA_fnPtr;
        dvmAddToReferenceTable_func dvmAddToReferenceTable_fnPtr;
        dvmDecodeIndirectRef_func dvmDecodeIndirectRef_fnPtr;
        dvmUseJNIBridge_func dvmUseJNIBridge_fnPtr;
        dvmFindInstanceField_func dvmFindInstanceField_fnPtr;
        dvmFindLoadedClass_func dvmFindLoadedClass_fnPtr;
        dvmDumpAllClasses_func dvmDumpAllClasses_fnPtr;

        dvmGetCurrentJNIMethod_func dvmGetCurrentJNIMethod_fnPtr;
        dvmLinearSetReadWrite_func dvmLinearSetReadWrite_fnPtr;

        dvmSetNativeFunc_func dvmSetNativeFunc_fnPtr;
        dvmCallJNIMethod_func dvmCallJNIMethod_fnPtr;

        dvmHashTableLock_func dvmHashTableLock_fnPtr;
        dvmHashTableUnlock_func dvmHashTableUnlock_fnPtr;
        dvmHashForeach_func dvmHashForeach_fnPtr;

        dvmDumpClass_func dvmDumpClass_fnPtr;
        dvmInstanceof_func dvmInstanceof_fnPtr;

        DalvikNativeMethod *dvm_dalvik_system_DexFile;
        DalvikNativeMethod *dvm_java_lang_Class;

        void *gDvm; // dvm globals !

        int done;
};
struct dexstuff_t 结构体包含了dalvik 虚拟机的重要的函数指针,这些函数指针的值从libdvm.so动态库里获取,有了这些指针,就可以在 native 环境里操纵java世界(比如加载类,生成对象,调用java函数,设置java类或函数属性等等),dexstuff_resolv_dvm 函数干的事情就是加载 libdvm.so 动态库并填充 dexstuff_t 结构体。

int dalvik_hook_setup(struct dalvik_hook_t *h, char *cls, char *meth, char *sig, int ns, void *func)
{
        if (!h)
                return 0;

        strcpy(h->clname, cls);
        strncpy(h->clnamep, cls+1, strlen(cls)-2);
        strcpy(h->method_name, meth);
        strcpy(h->method_sig, sig);
        h->n_iss = ns;
        h->n_rss = ns;
        h->n_oss = 0;
        h->native_func = func;

        h->sm = 0; // set by hand if needed

        h->af = 0x0100; // native, modify by hand if needed

        h->resolvm = 0; // don‘t resolve method on-the-fly, change by hand if needed

        h->debug_me = 0;

        return 1;
}
dalvik_hook_setup 做dalvik函数挂钩的准备工作,包括把目标类和目标函数的名字保存下来,把用于挂钩的native 函数的地址保存下来,设置 dalvik_hook_t 结构对象的初始值等。 

int dalvik_hook_setup(struct dalvik_hook_t *h, char *cls, char *meth, char *sig, int ns, void *func)
{

        strcpy(h->method_name, meth);
        strcpy(h->method_sig, sig);
        h->n_iss = ns;
        h->n_rss = ns;
        h->n_oss = 0;
        h->native_func = func;

        h->sm = 0; // set by hand if needed

        h->af = 0x0100; // native, modify by hand if needed

        h->resolvm = 0; // don‘t resolve method on-the-fly, change by hand if needed

        h->debug_me = 0;

        return 1;
}
{
        if (h->debug_me)
                log("dalvik_hook: class %s\n", h->clname)

        void *target_cls = dex->dvmFindLoadedClass_fnPtr(h->clname);
        if (h->debug_me)
                log("class = 0x%x\n", target_cls)

        // print class in logcat
        if (h->dump && dex && target_cls)
                dex->dvmDumpClass_fnPtr(target_cls, (void*)1);

        if (!target_cls) {
                if (h->debug_me)
                        log("target_cls == 0\n")
                return (void*)0;
        }

        if (h->method == 0) {
        }

        // constrcutor workaround, see "dalvik_prepare" below
        if (!h->resolvm) {
                h->cls = target_cls;
                h->mid = (void*)h->method;
        }

        if (h->debug_me)
                log("%s(%s) = 0x%x\n", h->method_name, h->method_sig, h->method)

        if (h->method) {
                h->insns = h->method->insns;

                if (h->debug_me) {
                        log("nativeFunc %x\n", h->method->nativeFunc)

                }

                h->iss = h->method->insSize;
                h->rss = h->method->registersSize;
                h->oss = h->method->outsSize;

                h->method->insSize = h->n_iss;
                h->method->registersSize = h->n_rss;
                h->method->outsSize = h->n_oss;

                if (h->debug_me) {
                        log("shorty %s\n", h->method->shorty)
                        log("name %s\n", h->method->name)
                        log("arginfo %x\n", h->method->jniArgInfo)
                }
                h->method->jniArgInfo = 0x80000000; // <--- also important
                if (h->debug_me) {
                        log("noref %c\n", h->method->noRef)
                        log("access %x\n", h->method->a)
                }
                h->access_flags = h->method->a;
                h->method->a = h->method->a | h->af; // make method native
                if (h->debug_me)
                        log("access %x\n", h->method->a)

                dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func);

                if (h->debug_me)
                        log("patched %s to: 0x%x\n", h->method_name, h->native_func)

                return (void*)1;
        }
        else {
                if (h->debug_me)
                        log("could NOT patch %s\n", h->method_name)
        }

        return (void*)0;
}

dalvik_hook 函数实现dalvik函数挂钩。分为几步:

step1, void *target_cls = dex->dvmFindLoadedClass_fnPtr(h->clname);  调用 dvmFindLoadedClass_fnPtr 函数获取目标类在虚拟机内部的指针,dvmFindLoadedClass_fnPtr这个函数是

dexstuff_resolv_dvm  获取的。
step2, h->method = dex->dvmFindVirtualMethodHierByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig); 根据函数名获取目标dalvik函数对象指针
step3,
h->iss = h->method->insSize;
h->rss = h->method->registersSize;
h->oss = h->method->outsSize;
h->method->insSize = h->n_iss;
h->method->registersSize = h->n_rss;
h->method->outsSize = h->n_oss;

保存目标Dalvik 函数寄存器环境,设置新的寄存器环境

step4, 

                h->method->jniArgInfo = 0x80000000; // <--- also important
                h->access_flags = h->method->a;
                h->method->a = h->method->a | h->af; // make method native
                dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func);

设置目标函数对象类型 access_flags 为 native ,并通过  dvmUseJNIBridge_fnPtr 函数将挂钩函数 h->native_func 设置为目标dalvik 函数对象真正执行的函数。这一步之后,虚拟机内部执行到目标java函数时,就会调用到 native 函数  my_dispatch,挂钩已经完成。

 

static void my_dispatch(JNIEnv *env, jobject obj, jobjectArray pdu)
{
        // load dex classes
        int cookie = dexstuff_loaddex(&d, "/data/local/tmp/ddiclasses.dex");
        log("libsmsdispatch: loaddex res = %x\n", cookie)
        if (!cookie)
                log("libsmsdispatch: make sure /data/dalvik-cache/ is world writable and delete data@local@[email protected]\n")
        void *clazz = dexstuff_defineclass(&d, "org/mulliner/ddiexample/SMSDispatch", cookie);
        log("libsmsdispatch: clazz = 0x%x\n", clazz)

        // call constructor and passin the pdu
        jclass smsd = (*env)->FindClass(env, "org/mulliner/ddiexample/SMSDispatch");
        jmethodID constructor = (*env)->GetMethodID(env, smsd, "<init>", "([[B)V");
        if (constructor) {
                jvalue args[1];
                args[0].l = pdu;

                jobject obj = (*env)->NewObjectA(env, smsd, constructor, args);
                log("libsmsdispatch: new obj = 0x%x\n", obj)

                if (!obj)
                        log("libsmsdispatch: failed to create smsdispatch class, FATAL!\n")
        }
        else {
                log("libsmsdispatch: constructor not found!\n")
        }

        // call original SMS dispatch method
        jvalue args[1];
        args[0].l = pdu;
        dalvik_prepare(&d, &dpdu, env);
        (*env)->CallVoidMethodA(env, obj, dpdu.mid, args);
        log("success calling : %s\n", dpdu.method_name)
        dalvik_postcall(&d, &dpdu);
}
my_dispatch 函数分几步,
step1, 加载我们自定义的 SMSDispatch dex 文件,dexstuff_defineclass 函数实现了这个过程
step2, 调用 JNIEnv 的 FindClass和GetMethodID函数,搜寻到step1加载的dex文件里的 SMSDispatch 类的构造函数, 再调用 NewObjectA 函数在目标 JNIEnv 环境里生成一个 SMSDispatch 实例
step3, 先调用 dalvik_prepare 函数将前面 hook 的函数回退,这样接下去自定义的 SMSDispatch 类可以执行真正的 com/android/internal/telephony/SMSDispatcher 类的 dispatchPdus 函数
step4, 调用 JNIEnv 的 CallVoidMethodA 函数,执行自定义的 SMSDispatch 类的函数
step5, 再调用 dalvik_postcall 函数恢复对 com/android/internal/telephony/SMSDispatcher 类的 dispatchPdus 函数的挂载


int dalvik_prepare(struct dexstuff_t *dex, struct dalvik_hook_t *h, JNIEnv *env)
{

        // this seems to crash when hooking "constructors"

        if (h->resolvm) {
                h->cls = (*env)->FindClass(env, h->clnamep);
                if (h->debug_me)
                        log("cls = 0x%x\n", h->cls)
                if (!h->cls)
                        return 0;
                if (h->sm)
                        h->mid = (*env)->GetStaticMethodID(env, h->cls, h->method_name, h->method_sig);
                else
                        h->mid = (*env)->GetMethodID(env, h->cls, h->method_name, h->method_sig);
                if (h->debug_me)
                        log("mid = 0x%x\n", h-> mid)
                if (!h->mid)
                        return 0;
        }

        h->method->insSize = h->iss;
        h->method->registersSize = h->rss;
        h->method->outsSize = h->oss;
        h->method->a = h->access_flags;
        h->method->jniArgInfo = 0;
        h->method->insns = h->insns;
}
dalvik_prepare 函数比较简单,将 dalvik_hook_t  保存的寄存器值、Method access_flags 标记、 insns 函数指针重新赋值回去,即恢复到了挂钩之前的状态

void dalvik_postcall(struct dexstuff_t *dex, struct dalvik_hook_t *h)
{
        h->method->insSize = h->n_iss;
        h->method->registersSize = h->n_rss;
        h->method->outsSize = h->n_oss;

        //log("shorty %s\n", h->method->shorty)
        //log("name %s\n", h->method->name)
        //log("arginfo %x\n", h->method->jniArgInfo)
        h->method->jniArgInfo = 0x80000000;
        //log("noref %c\n", h->method->noRef)
        //log("access %x\n", h->method->a)
        h->access_flags = h->method->a;
        h->method->a = h->method->a | h->af;
        //log("access %x\n", h->method->a)

        dex->dvmUseJNIBridge_fnPtr(h->method, h->native_func);

        if (h->debug_me)
                log("patched BACK %s to: 0x%x\n", h->method_name, h->native_func)
}
dalvik_postcall 函数跟 dalvik_hook 函数后半部分一致,通过对目标 Method 对象的成员进行赋值实现挂钩


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。