Android平台dalvik模式下java Hook框架ddi的分析(1)

人走茶凉 提交于 2020-10-02 08:48:15

本文博客地址: http://blog.csdn.net/qq1084283172/article/details/75710411

一、前 言

在前面的博客中已经学习了作者crmulliner编写的,针对Android系统的跨进程 inline Hook的实现即Android native Hook框架adbi的实现。Android Hook框架adbi主要是针对的Android的native函数进行inline Hook操作,那么如果需要对Android系统中Java编写的函数进行Hook,又该怎么操作呢?作者crmulliner后面又实现了针对Android的java函数进行Hook的框架ddi。adbi Hook框架和ddi java Hook框架的实现流程都差不多,首先实现root权限下Android跨进程的so注入,在so库文件被加载注入到目标进程中时,调用该so库文件的构造函数有一次代码执行的机会,利用这一次代码执行的机会既可以进行针对Android系统底层native函数的inline Hook或者.got Hook等,也可以进行针对Android系统的java函数进行dalvik Hook或者art Hook。Android平台的所有跨进程Hook都是基于Android系统root权限下的so注入和一次代码执行机会来实现的,只要能实现Android的跨进程注入(这个注入既可以是shellcode代码片段的注入也可以是so库文件的注入)就可以在目标进程中做很多的事情。


这里简要说下Android平台针对java函数的dalvik Hook(暂时不讨论基于art模式下的java函数的Hook,后面有时间再讨论)。dalvik虚拟机模式下java函数的Hook主要是修改存储java函数的信息结构体Method。在dalvik虚拟机中,java函数的形式是以Method结构体来表现的,每一个java编写的类成员函数最终在dalvik虚拟机中以Method结构体的形式存在。基于dalvik虚拟机的java Hook通过修改java函数的Method结构体中的函数属性值access_flags将一个java层函数修改为native属性的函数,这样一个java层实现的函数被修改为native层实现的函数,我们就可以将自定义编写的native函数替换掉原来的java层函数实现,从而实现基于dalvik虚拟机的java Hook。


二、Android平台java Hook框架ddi实现dalvik 模式下Hook的步骤

1)在so库文件被加载注入到目标进程中时,调用该so库文件.init段的构造函数获取dalvik模式下执行Hook java函数代码的机会;

2) dalvik虚拟机模式下,动态加载”libdvm.so”库文件并获取该动态加载的”libdvm.so”库文件中java Hook实现需要的导出函数的指针和导出全局变量,例如获取导出函数dvmFindLoadedClass、dvmFindVirtualMethodHierByDescriptor、dvmFindDirectMethodByDescriptor、dvmUseJNIBridge等的函数指针;

3)调用上面步骤中提到的”libdvm.so”库文件中的导出函数 dvmFindLoadedClass 获取被java Hook的java目标函数所在的目标类,然后再调用导出函数 dvmFindVirtualMethodHierByDescriptor、dvmFindDirectMethodByDescriptor 在查找到的目标类中获取被java Hook的目标函数(java层实现的函数)的信息结构体Method;

4)查找到被java Hook的目标函数(java层实现的函数)的信息结构体Method以后,先保存该目标函数的信息结构体Method的原始值,用以后面对目标函数进行java Hook操作后的恢复还原,如果没有保存目标函数的原始Method信息结构体值的话,想在java Hook操作之后再调用原来的java层实现的目标函数的话就不可能了;

5)对将被java Hook的目标函数的信息结构体Method进行修改即修改目标函数信息结构体Method的成员变量 access_flags 的值,实现将一个java层实现的方法改为 native 层实现的本地方法,这样一个java函数就变成了可以被我们自定义替换的native函数;

6)java层实现的目标函数被修改为native属性的本地方法以后,还需要对该被java Hook的目标函数信息结构体Method的成员变量 insSize(函数传参寄存器的数量)、outsSize(局部变量使用寄存器数量)、registersSize(函数调用使用的寄存器的总数)、jniArgInfo、native_func(改为jni桥接函数,例如 dvmResolveNativeMethod)的值进行修正;

7)为被java Hook的目标函数设置新的函数实现,按照被java Hook的目标函数的原始java函数声明实现对应函数声明的native层的jni函数(java Hook的替换函数),然后修改该目标函数的信息结构体Method的成员变量 insns 的值为该native层实现的jni函数,到这里java层实现的目标函数就替换为我们自定义实现的native层的jni函数,从而实现dalvik虚拟机模式下的java Hook。











提示:ddi 框架的 java Hook 实现原理和前面的博客《Android进程so注入Hook java方法》中提到的 java Hook 实现原理是一样的,只是在细节处理上稍有不同,ddi 框架处理的更好,不需要再次进行jni函数的注册。


三、Android平台java Hook框架ddi实现dalvik 模式下Hook的代码分析

1. 在dalvik虚拟机中每一个方法都由一个称作Method的结构体来表示(包括JNI方法),ddi框架实现java Hook就是通过修改目标函数的信息结构体Method来实现的。下面来了解一下Method结构体构成:

/*
 * A method.  We create one of these for every method in every class
 * we load, so try to keep the size to a minimum.
 *
 * Much of this comes from and could be accessed in the data held in shared
 * memory.  We hold it all together here for speed.  Everything but the
 * pointers could be held in a shared table generated by the optimizer;
 * if we're willing to convert them to offsets and take the performance
 * hit (e.g. "meth->insns" becomes "baseAddr + meth->insnsOffset") we
 * could move everything but "nativeFunc".
 */
 // Android 4.4.4r1源码文件路径 /dalvik/vm/oo/Object.h
struct Method {
    /* the class we are a part of */
    ClassObject* clazz;

    /* access flags; low 16 bits are defined by spec (could be u2?) */
    u4 accessFlags;

    /*
     * For concrete virtual methods, this is the offset of the method
     * in "vtable".
     *
     * For abstract methods in an interface class, this is the offset
     * of the method in "iftable[n]->methodIndexArray".
     */
    u2 methodIndex;

    /*
     * Method bounds; not needed for an abstract method.
     *
     * For a native method, we compute the size of the argument list, and
     * set "insSize" and "registerSize" equal to it.
     */
    u2 registersSize; /* ins + locals */
    u2 outsSize;
    u2 insSize;

    /* method name, e.g. "<init>" or "eatLunch" */
    const char* name;

    /*
     * Method prototype descriptor string (return and argument types).
     *
     * TODO: This currently must specify the DexFile as well as the proto_ids
     * index, because generated Proxy classes don't have a DexFile.  We can
     * remove the DexFile* and reduce the size of this struct if we generate
     * a DEX for proxies.
     */
    DexProto prototype;

    /* short-form method descriptor string */
    const char* shorty;

    /*
     * The remaining items are not used for abstract or native methods.
     * (JNI is currently hijacking "insns" as a function pointer, set
     * after the first call.  For internal-native this stays null.)
     */

    /* the actual code */
    const u2* insns; /* instructions, in memory-mapped .dex */

    /* JNI: cached argument and return-type hints */
    int jniArgInfo;

    /*
     * JNI: native method ptr; could be actual function or a JNI bridge.  We
     * don't currently discriminate between DalvikBridgeFunc and
     * DalvikNativeFunc; the former takes an argument superset (i.e. two
     * extra args) which will be ignored.  If necessary we can use
     * insns==NULL to detect JNI bridge vs. internal native.
     */
    DalvikBridgeFunc nativeFunc;

    /*
     * JNI: true if this static non-synchronized native method (that has no
     * reference arguments) needs a JNIEnv* and jclass/jobject. Libcore
     * uses this.
     */
    bool fastJni;

    /*
     * JNI: true if this method has no reference arguments. This lets the JNI
     * bridge avoid scanning the shorty for direct pointers that need to be
     * converted to local references.
     *
     * TODO: replace this with a list of indexes of the reference arguments.
     */
    bool noRef;

    /*
     * JNI: true if we should log entry and exit. This is the only way
     * developers can log the local references that are passed into their code.
     * Used for debugging JNI problems in third-party code.
     */
    bool shouldTrace;

    /*
     * Register map data, if available.  This will point into the DEX file
     * if the data was computed during pre-verification, or into the
     * linear alloc area if not.
     */
    const RegisterMap* registerMap;

    /* set if method was called during method profiling */
    bool inProfile;
};

(1)clazz:当前方法所在的类;

(2)accessFlags:当前方法所具有的属性,例如:类访问属性、是否静态函数等,accessFlags属性值 的定义如下:

/*
 * access flags and masks; the "standard" ones are all <= 0x4000
 *
 * Note: There are related declarations in vm/oo/Object.h in the ClassFlags
 * enum.
 */
 // Android 4.4.4r1 源码路径 /dalvik/libdex/DexFile.h
enum {
    ACC_PUBLIC       = 0x00000001,       // class, field, method, ic
    ACC_PRIVATE      = 0x00000002,       // field, method, ic
    ACC_PROTECTED    = 0x00000004,       // field, method, ic
    ACC_STATIC       = 0x00000008,       // field, method, ic
    ACC_FINAL        = 0x00000010,       // class, field, method, ic
    ACC_SYNCHRONIZED = 0x00000020,       // method (only allowed on natives)
    ACC_SUPER        = 0x00000020,       // class (not used in Dalvik)
    ACC_VOLATILE     = 0x00000040,       // field
    ACC_BRIDGE       = 0x00000040,       // method (1.5)
    ACC_TRANSIENT    = 0x00000080,       // field
    ACC_VARARGS      = 0x00000080,       // method (1.5)
    ACC_NATIVE       = 0x00000100,       // method
    ACC_INTERFACE    = 0x00000200,       // class, ic
    ACC_ABSTRACT     = 0x00000400,       // class, method, ic
    ACC_STRICT       = 0x00000800,       // method
    ACC_SYNTHETIC    = 0x00001000,       // field, method, ic
    ACC_ANNOTATION   = 0x00002000,       // class, ic (1.5)
    ACC_ENUM         = 0x00004000,       // class, field, ic (1.5)
    ACC_CONSTRUCTOR  = 0x00010000,       // method (Dalvik only)
    ACC_DECLARED_SYNCHRONIZED =
                       0x00020000,       // method (Dalvik only)
    ACC_CLASS_MASK =
        (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT
                | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),
    ACC_INNER_CLASS_MASK =
        (ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC),
    ACC_FIELD_MASK =
        (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                | ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM),
    ACC_METHOD_MASK =
        (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
                | ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE
                | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR
                | ACC_DECLARED_SYNCHRONIZED),
};

上面的枚举变量中定义了java的类、类成员方法、类成员变量的单个函数属性值和组合方法并在右边的注释中给出了函数属性值的使用范围。类ClassObject、成员方法Method以及成员变量Field中 accessFlags 的具体含义和使用方法如下图所示:
这里写图片描述


上面的示意图来自作者Roland_Sun的博客《Android平台下Dalvik层hook框架ddi的研究》,作者的博客写的很不错,借用一下。

(3)methodIndex:对于已经实现了的虚函数来说,这个是该方法在类虚函数表(vtable)中的偏移; 对于类接口(interface)的抽象接口函数来说,这个是该方法在对应的接口表(假设这个方法是定义在类继承的第n+1个接口中,则表示iftable[n]->methodIndexArray)中的序号;如果只是类的普通函数,这个表示的是该函数在类成员函数列表中的序号;

(4)registersSize:当前方法被调用需要用到的总寄存器数量即 insSize(函数传参使用的寄存器数量)和 outsSize(函数中局部变量使用的寄存器数量)之和;

(5)outsSize:当前方法调用时,本地局部变量需要用到的寄存器数量;对于java实现的函数而言,由于Android系统java方法执行的 dalvik虚拟机 是基于寄存器设计的,在java函数调用时,本地局部变量的传递需要用到寄存器;而对于jni实现的native函数而言,native函数的执行不是有dalvik虚拟机来执行的并且native函数调用局部变量的申请和保存是基于堆栈来管理的,因此对于native函数来说,outsSize的值为0;

(6)insSize:当前方法在调用时,传递函数参数需要用的寄存器的数量;

(7)name:当前方法的名称字符串;

(8)prototype:当前方法的协议描述字符串也就当前方法的函数签名字符串(包括方法调用参数类型、顺序还有返回类型的描述);

(9)shorty:当前方法的短协议描述字符串,一个字符代表一种数据类型;

(10)insns:对于java方法来说,insns指向的是该java方法dalvik指令的内存存放地址(即insns指向的是dex文件被加载映射到内存中存放该java方法的dalvik指令的内存地址,dalvik虚拟机在执行代码指令的时,到insns内存地址处来取代码指令);对于普通native方法(开发自定义的jni函数)来说,insns指向的是该native层实现jni函数的调用地址;对于dalvik虚拟机自带的native函数(Internal Native)来说,insns的值为null;

(11)jniArgInfo:缓存了一些预先计算好的信息,从而不需要在方法调用的时再通过方法的参数和返回值进行实时计算,方便jni方法的调用,提高了调用的速度。如果第一位为1(即0x80000000),则dalvik虚拟机在执行时会忽略后面的所有信息,强制在方法调用时进行实时计算。意思就是在函数调用之前,进行一些函数调用需要的函数参数 和返回值的缓存处理,方面后面函数的调用,提高运行的效率;

(12)nativeFunc:对于dalvik虚拟机自带的内部native函数(Internal Native)来说,该变量指向的是该 Internal Native 方法的实际函数调用地址;对于普通native函数(开发者编写的jni函数)来说,该变量指向的是该native层jni函数调用需要的 jni桥接函数DalvikBridgeFunc 或者 DalvikNativeFunc 并且可以通过判断 insns==NULL 与否来判断该native函数是普通jni的native函数还是dalvik虚拟机自带的内部Internal Native 函数;

(13)fastJni:是否采用 fastJni模式 进行jni函数的调用标志,fastJni模式下直接进行jni函数的调用,省去了很多的跳转调用,高效,但是fastJni模式调用是有要求的,比如是静态,而且非synchronized函数;

(14)registerMap:表示当前方法在每一个GC安全点上,有哪些寄存器其存放的数值是指向某个对象的引用,它主要是给dalvik虚拟机做精确垃圾收集使用的,没有被引用对象的内存将被回收。



2. ddi框架中dalvik模式下java Hook需要用到的信息结构体 dalvik_hook_t ,该结构体主要的作用是进行被java Hook的java目标函数的信息保存和一些java Hook参数的设置和缓存预留。

// 被dalvik Hook的目标函数dalvik Hook相关的信息结构体
struct dalvik_hook_t
{
    // 被dalvik Hook的目标函数所在类的名称
    char clname[256];
    // 被dalvik Hook的目标函数所在类的协议名称字符串,例如:"java/lang/StringBuffer"
    char clnamep[256];

    // 被dalvik Hook的目标函数的名称
    char method_name[256];
    // 被dalvik Hook的目标函数的签名
    char method_sig[256];

    // 被dalvik Hook的目标函数的信息结构体Method
    Method *method;
    // 记录被dalvik Hook的目标函数是否是静态函数
    int sm; // static method

    // original values, saved before patching
    // 保存被dalvik Hook的目标函数原始的寄存器数量
    int iss; // 目标函数(java层)传参使用的寄存器数量
    int rss; // 目标函数(java层)使用的总的寄存器数量
    int oss; // 目标函数(java层)局部变量使用的寄存器数量

    // 保存被dalvik Hook的目标函数原始的函数属性值
    int access_flags;
    // 保存被dalvik Hook的目标函数原始的dalvik代码指令的首地址
    void *insns; // dalvik code

    // native values
    // 记录java层目标函数被dalvik Hook后(改为native函数)的寄存器数量
    int n_iss; // == n_rss,改为native函数后函数传参使用的寄存器数量
    int n_rss; // num argument (+ 1, if non-static method),改为native函数后使用的总的寄存器数量
    int n_oss; // 0,改为native函数后局部变量使用的寄存器的数量,由于native函数使用堆栈传参,因此为0
    // java层目标函数被dalvik Hook后(改为native函数)需要的自定义替换函数native_func
    void *native_func;
    // java层目标函数被dalvik Hook后(改为native函数)的函数属性值0x0100
    int af; // access flags modifier

    // 是否记录被dalvik Hook的java层目标函数的jclass和jmethodID值
    int resolvm;
    // for the call
    // 记录被dalvik Hook的java层目标函数的jclass值
    jclass cls;
    // 记录被dalvik Hook的java层目标函数的mid
    jmethodID mid;

    // debug stuff
    int dump;      // call dvmDumpClass() while patching(是否打印被dalvik Hook类的日志开关)
    int debug_me;  // print debug while operating on this method(是否打印调试log日志的开关)
};

3. 上面提到的结构体 Method 和 dalvik_hook_t 在后面的代码分析中还会提到,比较重要,需要好好的理解一下。现在开始详细分析一下 ddi框架 的实现流程,ddi框架 Hook的实现也是基于root权限下Android跨进程so库文件的注入实现的,在目标进程加载注入的so库文件时,调用.init段构造函数实现dalvik虚拟机模式下的java Hook,ddi Hook框架的so库文件注入工具也是使用 hijack,ddi Hook框架是在修改adbi Hook框架源码的基础上实现的。

// 在android系统libc.so库中的epoll_wait函数被inline Hook后的自定义Hook函数my_epoll_wait中,进行针对dalvik的Hook操作
static int my_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
    // 被inline Hook的epoll_wait函数原型的声明
    int (*orig_epoll_wait)(int epfd, struct epoll_event *events, int maxevents, int timeout);

    // 获取epoll_wait函数的原始调用地址
    orig_epoll_wait = (void*)eph.orig;
    // 恢复被Hook的epoll_wait函数的调用
    hook_precall(&eph);

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 下面是新增的针对Android系统的dalvik进行Hook操作的代码

    // 动态加载"libdvm.so"库文件并获取该加载的动态库中dalvik Hook实现需要的导出函数的调用地址和导出全局变量
    // dalvik Hook实现需要的导出函数的调用地址和导出全局变量的信息保存在结构体变量dexstuff_t d 中
    dexstuff_resolv_dvm(&d);
    // insert hooks
    do_patch();

    // call dump class (demo)
    dalvik_dump_class(&d, "Ljava/lang/String;");

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    // 对原始的epoll_wait函数进行调用
    int res = orig_epoll_wait(epfd, events, maxevents, timeout);

    return res;
}

// 设置自定义代码被执行的入口点
void __attribute__ ((constructor)) my_init(void);

// so库文件被加载时,首先执行构造函数my_init,在执行so的构造函数时进行java的Hook
void my_init(void)
{
    log("libstrmon: started\n")

    // set to 1 to turn on, this will be noisy(调试开关)
    debug = 1;

    // set log function for  libbase (very important!)(设置libbase的打印log日志的函数)
    set_logfunction(my_log2);

    // set log function for libdalvikhook (very important!)(设置libdalvikhook的log日志打印的函数)
    dalvikhook_set_logfunction(my_log2);

    // adbi中提到的inline Hook实现的Hook函数
    hook(&eph, getpid(), "libc.", "epoll_wait", my_epoll_wait, 0);
}

提示:ddi Hook框架的作者偷了个懒直接将dalvik模式下的java Hook的处理代码放在了inline Hook替换epoll_wait函数的Hook自定义函数my_epoll_wait中,其实可以直接将dalvik模式下的java Hook的处理代码放在被注入加载的so库文件.init段的 构造函数my_init中 ,在so库文件被加载注入到目标进程中时调用该构造函数实现dalvik模式下的java Hook目标java函数。

4. ddi Hook框架的主要代码实现流程如下:

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 下面是新增的针对Android系统的dalvik进行Hook操作的代码

    // 1.动态加载"libdvm.so"库文件并获取该加载的动态库中dalvik Hook实现需要的导出函数的调用地址和导出全局变量
    // dalvik Hook实现需要的导出函数的调用地址和导出全局变量的信息保存在结构体变量dexstuff_t d 中
    dexstuff_resolv_dvm(&d);

    // insert hooks
    // 2.对指定的java层目标函数进行dalvik Hook操作
    do_patch();

    // call dump class (demo)
    dalvik_dump_class(&d, "Ljava/lang/String;");

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

5. 在对目标java函数进行java Hook之前,调用dexstuff_resolv_dvm函数,先 dlopen 加载动态库文件libdvm.so,调用 dlsym 函数获取libdvm.so库文件中java Hook需要的导出函数dvm_dalvik_system_DexFile、dvm_java_lang_Class、dvmStringFromCStr、dvmFindLoadedClass、dvmFindVirtualMethodHierByDescriptor、dvmFindDirectMethodByDescriptor、dvmUseJNIBridge、dvmGetCurrentJNIMethod等的调用地址(VA);dvm_dalvik_system_DexFile函数用于dex文件的加载和指定类的加载,dvmStringFromCStr将C语言格式的字符串转换成dalvik虚拟机能使用的字符串,dvmFindLoadedClass进行目标类的查找,dvmFindVirtualMethodHierByDescriptor和dvmFindDirectMethodByDescriptor进行指定类成员函数的查找,dvmUseJNIBridge函数实现修改Method结构体的成员变量native_func为jni调用的桥接跳转函数DalvikBridgeFunc 或者 DalvikNativeFunc以及设置Method结构体的成员变量insns指向替换被java Hook的目标函数的native层自定义实现jni函数的调用地址。

// 获取加载的动态库中导出函数的调用地址
static void* mydlsym(void *hand, const char *name)
{
    void* ret = dlsym(hand, name);
    log("%s = 0x%x\n", name, ret)

    return ret;
}


// 动态加载"libdvm.so"库文件并获取该加载的动态库中dalvik Hook实现需要的导出函数的调用地址和导出全局变量
void dexstuff_resolv_dvm(struct dexstuff_t *d)
{
    // 动态加载"libdvm.so"库文件并保存文件句柄
    d->dvm_hand = dlopen("libdvm.so", RTLD_NOW);
    log("dvm_hand = 0x%x\n", d->dvm_hand)

    // 获取加载的文件句柄成功的情况
    if (d->dvm_hand) {

        // 获取加载的"libdvm.so"库文件中导出函数dvm_dalvik_system_DexFile的调用地址
        d->dvm_dalvik_system_DexFile = (DalvikNativeMethod*) mydlsym(d->dvm_hand, "dvm_dalvik_system_DexFile");
        // 获取加载的"libdvm.so"库文件中导出函数dvm_java_lang_Class的调用地址
        d->dvm_java_lang_Class = (DalvikNativeMethod*) mydlsym(d->dvm_hand, "dvm_java_lang_Class");

        // 获取加载的"libdvm.so"库文件中导出函数dvmThreadSelf的调用地址
        d->dvmThreadSelf_fnPtr = mydlsym(d->dvm_hand, "_Z13dvmThreadSelfv");
        if (!d->dvmThreadSelf_fnPtr)
            d->dvmThreadSelf_fnPtr = mydlsym(d->dvm_hand, "dvmThreadSelf");

        // 获取加载的"libdvm.so"库文件中导出函数dvmStringFromCStr的调用地址
        d->dvmStringFromCStr_fnPtr = mydlsym(d->dvm_hand, "_Z32dvmCreateStringFromCstrAndLengthPKcj");
        // 获取加载的"libdvm.so"库文件中导出函数dvmGetSystemClassLoader的调用地址
        d->dvmGetSystemClassLoader_fnPtr = mydlsym(d->dvm_hand, "_Z23dvmGetSystemClassLoaderv");
        // 获取加载的"libdvm.so"库文件中导出函数dvmIsClassInitialized的调用地址
        d->dvmIsClassInitialized_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmIsClassInitializedPK11ClassObject");
        // 获取加载的"libdvm.so"库文件中导出函数dvmInitClass的调用地址
        d->dvmInitClass_fnPtr = mydlsym(d->dvm_hand, "dvmInitClass");

        // 获取加载的"libdvm.so"库文件中导出函数dvmFindVirtualMethodHierByDescriptor的调用地址
        d->dvmFindVirtualMethodHierByDescriptor_fnPtr = mydlsym(d->dvm_hand, "_Z36dvmFindVirtualMethodHierByDescriptorPK11ClassObjectPKcS3_");
        if (!d->dvmFindVirtualMethodHierByDescriptor_fnPtr)
            d->dvmFindVirtualMethodHierByDescriptor_fnPtr = mydlsym(d->dvm_hand, "dvmFindVirtualMethodHierByDescriptor");

        // 获取加载的"libdvm.so"库文件中导出函数dvmFindDirectMethodByDescriptor的调用地址
        d->dvmFindDirectMethodByDescriptor_fnPtr = mydlsym(d->dvm_hand, "_Z31dvmFindDirectMethodByDescriptorPK11ClassObjectPKcS3_");
        if (!d->dvmFindDirectMethodByDescriptor_fnPtr)
            d->dvmFindDirectMethodByDescriptor_fnPtr = mydlsym(d->dvm_hand, "dvmFindDirectMethodByDescriptor");

        // 获取加载的"libdvm.so"库文件中导出函数dvmIsStaticMethod的调用地址
        d->dvmIsStaticMethod_fnPtr = mydlsym(d->dvm_hand, "_Z17dvmIsStaticMethodPK6Method");
        // 获取加载的"libdvm.so"库文件中导出函数dvmAllocObject的调用地址
        d->dvmAllocObject_fnPtr = mydlsym(d->dvm_hand, "dvmAllocObject");
        // 获取加载的"libdvm.so"库文件中导出函数dvmCallMethodV的调用地址
        d->dvmCallMethodV_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmCallMethodVP6ThreadPK6MethodP6ObjectbP6JValueSt9__va_list");
        // 获取加载的"libdvm.so"库文件中导出函数dvmCallMethodA的调用地址
        d->dvmCallMethodA_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmCallMethodAP6ThreadPK6MethodP6ObjectbP6JValuePK6jvalue");
        // 获取加载的"libdvm.so"库文件中导出函数dvmAddToReferenceTable的调用地址
        d->dvmAddToReferenceTable_fnPtr = mydlsym(d->dvm_hand, "_Z22dvmAddToReferenceTableP14ReferenceTableP6Object");

        // 获取加载的"libdvm.so"库文件中导出函数dvmSetNativeFunc的调用地址
        d->dvmSetNativeFunc_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmSetNativeFuncP6MethodPFvPKjP6JValuePKS_P6ThreadEPKt");
        // 获取加载的"libdvm.so"库文件中导出函数dvmUseJNIBridge的调用地址
        d->dvmUseJNIBridge_fnPtr = mydlsym(d->dvm_hand, "_Z15dvmUseJNIBridgeP6MethodPv");
        if (!d->dvmUseJNIBridge_fnPtr)
            d->dvmUseJNIBridge_fnPtr = mydlsym(d->dvm_hand, "dvmUseJNIBridge");

        // 获取加载的"libdvm.so"库文件中导出函数dvmDecodeIndirectRef的调用地址
        d->dvmDecodeIndirectRef_fnPtr =  mydlsym(d->dvm_hand, "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject");
        // 获取加载的"libdvm.so"库文件中导出函数dvmLinearSetReadWrite的调用地址
        d->dvmLinearSetReadWrite_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmLinearSetReadWriteP6ObjectPv");
        // 获取加载的"libdvm.so"库文件中导出函数dvmGetCurrentJNIMethod的调用地址
        d->dvmGetCurrentJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z22dvmGetCurrentJNIMethodv");
        // 获取加载的"libdvm.so"库文件中导出函数dvmFindInstanceField的调用地址
        d->dvmFindInstanceField_fnPtr = mydlsym(d->dvm_hand, "_Z20dvmFindInstanceFieldPK11ClassObjectPKcS3_");

        //d->dvmCallJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z21dvmCheckCallJNIMethodPKjP6JValuePK6MethodP6Thread");
        // 获取加载的"libdvm.so"库文件中导出函数dvmCallJNIMethod的调用地址
        d->dvmCallJNIMethod_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread");

        // 获取加载的"libdvm.so"库文件中导出函数dvmDumpAllClasses的调用地址
        d->dvmDumpAllClasses_fnPtr = mydlsym(d->dvm_hand, "_Z17dvmDumpAllClassesi");
        // 获取加载的"libdvm.so"库文件中导出函数dvmDumpClass的调用地址
        d->dvmDumpClass_fnPtr = mydlsym(d->dvm_hand, "_Z12dvmDumpClassPK11ClassObjecti");

        // 获取加载的"libdvm.so"库文件中导出函数dvmFindLoadedClass的调用地址
        d->dvmFindLoadedClass_fnPtr = mydlsym(d->dvm_hand, "_Z18dvmFindLoadedClassPKc");
        if (!d->dvmFindLoadedClass_fnPtr)
            d->dvmFindLoadedClass_fnPtr = mydlsym(d->dvm_hand, "dvmFindLoadedClass");

        // 获取加载的"libdvm.so"库文件中导出函数dvmHashTableLock的调用地址
        d->dvmHashTableLock_fnPtr = mydlsym(d->dvm_hand, "_Z16dvmHashTableLockP9HashTable");
        // 获取加载的"libdvm.so"库文件中导出函数dvmHashTableUnlock的调用地址
        d->dvmHashTableUnlock_fnPtr = mydlsym(d->dvm_hand, "_Z18dvmHashTableUnlockP9HashTable");
        // 获取加载的"libdvm.so"库文件中导出函数dvmHashForeach的调用地址
        d->dvmHashForeach_fnPtr = mydlsym(d->dvm_hand, "_Z14dvmHashForeachP9HashTablePFiPvS1_ES1_");
        // 获取加载的"libdvm.so"库文件中导出函数dvmInstanceof的调用地址
        d->dvmInstanceof_fnPtr = mydlsym(d->dvm_hand, "_Z13dvmInstanceofPK11ClassObjectS1_");

        // 获取加载的"libdvm.so"库文件中导出全部变量gDvm的调用地址
        d->gDvm = mydlsym(d->dvm_hand, "gDvm");
    }
}

6. 调用 do_patch函数 实现对指定的java目标函数进行java Hook处理,do_patch函数调用主要有 dalvik_hook_setup函数 和 dalvik_hook函数组成,dalvik_hook_setup函数负责dalvik Hook的预处理,dalvik_hook函数实现对java目标函数的dalvik Hook操作。

// 对指定的java层目标函数进行dalvik Hook操作
void do_patch()
{
    log("do_patch()\n")

    // 对StringBuffer.toString()进行dalvik Hook操作
    dalvik_hook_setup(&sb1, "Ljava/lang/StringBuffer;",  "toString",  "()Ljava/lang/String;", 1, sb1_tostring);
    dalvik_hook(&d, &sb1);

    dalvik_hook_setup(&sb20, "Ljava/lang/StringBuilder;",  "toString",  "()Ljava/lang/String;", 1, sb20_tostring);
    dalvik_hook(&d, &sb20);

    dalvik_hook_setup(&sb2, "Ljava/lang/String;", "compareTo", "(Ljava/lang/String;)I", 2, sb2_compareto);
    dalvik_hook(&d, &sb2);

    dalvik_hook_setup(&sb3, "Ljava/lang/String;", "compareToIgnoreCase", "(Ljava/lang/String;)I", 2, sb3_comparetocase);
    dalvik_hook(&d, &sb3);

    dalvik_hook_setup(&sb13, "Ljava/lang/String;", "equalsIgnoreCase", "(Ljava/lang/String;)Z", 2, sb13_equalsIgnoreCase);
    dalvik_hook(&d, &sb13);

    dalvik_hook_setup(&sb6, "Ljava/lang/String;", "contains", "(Ljava/lang/CharSequence;)Z", 2, sb6_contains);
    dalvik_hook(&d, &sb6);

    dalvik_hook_setup(&sb14, "Ljava/lang/String;", "contentEquals", "(Ljava/lang/StringBuffer;)Z", 2, sb14_contentEquals);
    dalvik_hook(&d, &sb14);

    dalvik_hook_setup(&sb7, "Ljava/lang/String;", "indexOf", "(Ljava/lang/String;I)I", 3, sb7_indexof);
    dalvik_hook(&d, &sb7);

    dalvik_hook_setup(&sb11, "Ljava/lang/StringBuffer;", "indexOf", "(Ljava/lang/String;I)I", 3, sb11_indexof);
    dalvik_hook(&d, &sb11);

    dalvik_hook_setup(&sb9, "Ljava/lang/String;", "endsWith", "(Ljava/lang/String;)Z", 2, sb9_endswith);
    dalvik_hook(&d, &sb9);

    dalvik_hook_setup(&sb10, "Ljava/lang/String;", "startsWith", "(Ljava/lang/String;I)Z", 3, sb10_startswith);
    dalvik_hook(&d, &sb10);

    dalvik_hook_setup(&sb8, "Ljava/lang/String;", "matches", "(Ljava/lang/String;)Z", 2, sb8_matches);
    dalvik_hook(&d, &sb8);

    dalvik_hook_setup(&sb5, "Ljava/lang/Class;", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", 3, sb5_getmethod);
    dalvik_hook(&d, &sb5);
}

7. 调用dalvik_hook_setup函数保存被java Hook的目标函数所在类的名称和协议字符串,被java Hook的目标函数的名称和函数签名,预先计算、保存java目标函数被修改为native函数之后寄存器的数量值、替换java目标函数的自定义native函数的调用地址以及一些java Hook中标识的初始化设置。

// 源码文件 dalvik_hook.c
/*
 * dalvik_hook_t用于记录被dalvik Hook的java类成员方法的有关信息
 * cls为被dalvik Hook的java类成员方法所在的类的签名
 * meth为被dalvik Hook的java类成员方法的名称
 * sig为被dalvik Hook的java类成员方法的函数签名
 * ns为java成员方法被修改为native层实现的方法后调用,传参需要的寄存器的个数
 * func为被dalvik Hook的java类成员方法的替换自定义Hook函数
 * 调用实例:dalvik_hook_setup(&sb1, "Ljava/lang/StringBuffer;",  "toString",  "()Ljava/lang/String;", 1, sb1_tostring);
 *
 * 注意:
 * 1.Android的dalvik虚拟机是基于寄存器的,因此java语言实现的函数在执行的时候需要预先计算出函数调用时传递参数需要的寄存器数量insSize以及函数内部
 * 局部变量要用到的寄存器的数量outsSize,需要的寄存器总数量为registersSize=outsSize+insSize。
 * 2.java的类成员方法中非静态的成员方法的第一个函数参数是指向本身的对象指针然后是其他的传入参数,静态成员方法不存在这样的问题。
 * 3.当java层的类成员方法被修改为native属性的成员方法后,由于native属性方法的局部变量的内存申请是通过堆栈来完成的,因此在计算该方法的寄存器数量时,
 * 局部变量用到的寄存器的数量outsSize为0,并且总的寄存器数量与传参寄存器的数量相同即registersSize=insSize。
 *
 */
// 记录java层目标函数被dalvik Hook操作相关需要的结构体信息
int dalvik_hook_setup(struct dalvik_hook_t *h, char *cls, char *meth, char *sig, int ns, void *func)
{
    if (!h)
        return 0;

    // 保存被dalvik Hook的类成员方法所在的类的名称
    strcpy(h->clname, cls);
    // 保存被dalvik Hook的类成员方法所在的类的协议名称,如:"java/lang/StringBuffer"
    strncpy(h->clnamep, cls+1, strlen(cls)-2);

    // 保存被dalvik Hook的类成员方法的名称
    strcpy(h->method_name, meth);
    // 保存被dalvik Hook的类成员方法的函数签名
    strcpy(h->method_sig, sig);

    // 提示:下面的部分变量值主要用于该java层的类成员方法meth被修改为native层后寄存器记录变量值的修改
    // dalvik Hook的实现其实就是将java层实现的类成员函数修改为native层实现的自定义Hook函数
    // 保存被dalvik Hook后java层类成员方法的寄存器数量

    // 被dalvik Hook后该native层的函数传参需要的寄存器数量
    h->n_iss = ns;
    // 被dalvik Hook后该native层的函数需要的总的寄存器的数量
    h->n_rss = ns;
    // 被dalvik Hook后该native层的函数局部变量需要的寄存器的数量
    h->n_oss = 0;
    // 被dalvik Hook后该native层的函数被替换的自定义Hook函数
    h->native_func = func;

    // 记录被dalvik Hook的函数是否是静态成员方法的标志,默认为非静态成员方法即0
    h->sm = 0; // set by hand if needed

    // java类成员函数的类型属性标志accessFlags,其中accessFlags=0x0100表示该函数为native层实现的函数
    h->af = 0x0100; // native, modify by hand if needed

    // 记录是否需要保存查找到,将被dalvik Hook的目标函数所在类的指针和方法结构体指针的标志,默认为0-需要
    h->resolvm = 0; // don't resolve method on-the-fly, change by hand if needed

    // debug调试打印Log日志的开关,默认为0-不打印日志
    h->debug_me = 0;

    return 1;
}

8. 调用dalvik_hook函数实现对java目标函数的dalvik Hook,先调用libdvm.so库文件的 导出函数dvmFindLoadedClass 查找到被java Hook的java目标函数所在的目标类,再调用libdvm.so库文件的 导出函数dvmFindVirtualMethodHierByDescriptor 或者 dvmFindDirectMethodByDescriptor 查找到被java Hook的目标函数的信息结构体 Method,在修改该目标函数的信息结构体 Method之前先 保存备份 该被java Hook目标函数的所在类、原始方法信息结构体Method等信息,然后修改该被java Hook的目标函数的信息结构体 Method 的成员变量 access_flags 的值将一个java函数改为native属性jni实现的函数,修正目标函数被改后函数调用需要的寄存器数量(registersSize == insSize,outsSize=0),修正目标函数被改后函数调用需要的jniArgInfo的值为0x80000000,修正目标函数被改后函数调用 insns 和 native_func 的值,这两个变量是通过调用libdvm.so库文件中的 导出函数dvmUseJNIBridge 来修改的,作用就是将 nativeFunc域 改成指向一个JNI桥跳转函数(dvmCallJNIMethod )并将 insns域 改成替换被java Hook目标函数的自定义native属性jni函数的调用地址,dvmUseJNIBridge函数这么调用相当于 对被修改后的目标函数(native属性)进行了 RegisterNatives 操作。

// 修改java层目标函数为native属性的native实现的jni函数并修正该函数正确调用相关的Method结构体的信息
void* dalvik_hook(struct dexstuff_t *dex, struct dalvik_hook_t *h)
{
    if (h->debug_me)
        log("dalvik_hook: class %s\n", h->clname)

    // 调用dalvik虚拟机的函数dvmFindLoadedClass获取被dalvik hook的目标函数所在类的指针
    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){

        // 打印找到被dalvik hook的目标函数所在类的名称信息
        dex->dvmDumpClass_fnPtr(target_cls, (void*)1);
    }

    // 判断被dalvik hook的目标函数所在类的指针是否为null并打印日志
    if (!target_cls) {

        if (h->debug_me)
            log("target_cls == 0\n")

        return (void*)0;
    }

    // 在被dalvik hook的目标函数所在类的 非静态成员函数 中查找将被dalvik hook的java层目标函数
    h->method = dex->dvmFindVirtualMethodHierByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig);
    // 如果查找失败
    if (h->method == 0) {

        // 在被dalvik hook的目标函数所在类的 静态成员函数 中查找将被dalvik hook的java层目标函数
        h->method = dex->dvmFindDirectMethodByDescriptor_fnPtr(target_cls, h->method_name, h->method_sig);
    }

    // constrcutor workaround, see "dalvik_prepare" below
    // 保存查找到的,将被dalvik hook的java层目标函数所在类指针和方法结构体指针的信息,用以dalvik Hook后的还原
    if (!h->resolvm) {

        // 保存查找到将被dalvik hook的java层目标函数所在类的指针
        h->cls = target_cls;
        // 保存查找到将被dalvik hook的java层目标函数的方法体结构指针
        h->mid = (void*)h->method;
    }

    // 打印查找到被dalvik hook的java层目标函数的信息
    if (h->debug_me)
        log("%s(%s) = 0x%x\n", h->method_name, h->method_sig, h->method)

    // 进行目标函数被dalvik Hook操作的修改
    if (h->method) {

/*
 * dalvik虚拟机中方法结构体Method的提示:
 * -----------------------------------------------------------------------------------------------------------
 * 1.method->insns:
 * 如果这个方法不是Native层实现即java层实现的函数,则这里存放了指向该方法具体的Dalvik指令的指针
 * (这个变量指向的是实际加载到内存中的Dalvik指令代码,而不是在Dex文件中的);
 * 如果这个方法是一个Dalvik虚拟机自带的Native函数(即Internal Native函数),则这个变量会是Null。
 * 如果这个方法是一个普通的Native函数即jni实现的自定义函数,则这里存放了指向jni实际函数机器码的首地址;
 * -----------------------------------------------------------------------------------------------------------
 * 2.method->jniArgInfo:
 * 这个变量记录了一些预先计算好的函数参数信息,从而不需要在函数调用的时候再通过方法的参数和返回值实时计算了,
 * 方便了JNI的调用,提高了调用的速度。如果第一位为1(即0x80000000),则Dalvik虚拟机会忽略后面的所有信息,强制在调用时实时计算;
 * -----------------------------------------------------------------------------------------------------------
 * 3.method->nativeFunc:
 * 如果这个方法是一个Dalvik虚拟机自带的Native函数(Internal Native)的话,则这里存放了指向JNI实际函数机器码的首地址。
 * 如果这个方法是一个普通的Native函数,则这里将指向一个中间的跳转JNI桥(Bridge)代码;
 * -----------------------------------------------------------------------------------------------------------
 * 4.通过method->accessFlags可以判断一个方法是不是Native的(和0x00100相与),如果是Native方法的话,就直接执行nativeFunc所指向的本地代码,
 * 如果不是Native方法的话,就执行insns所指向的Dalvik代码。
 */
        // 保存被dalvik Hook的java层目标函数的dalvik字节码指针method->insns
        h->insns = h->method->insns;
        if (h->debug_me) {

            // 打印被dalvik Hook操作的java层目标函数的原始信息
            log("nativeFunc %x\n", h->method->nativeFunc)
            log("insSize = 0x%x  registersSize = 0x%x  outsSize = 0x%x\n", h->method->insSize, h->method->registersSize, h->method->outsSize)
        }

        // 保存被dalvik Hook的java层目标函数的寄存器数量信息,用以后面dalvik Hook的恢复还原

        // 被dalvik Hook的java层目标函数的传参寄存器的个数
        h->iss = h->method->insSize;
        // 保存被dalvik Hook的java层目标函数的局部变量使用的寄存器个数
        h->oss = h->method->outsSize;
        // 保存被dalvik Hook的java层目标函数的总寄存器个数
        h->rss = h->method->registersSize;

        // 修改被dalvik Hook的java层目标函数的寄存器个数为该目标函数为native属性时的正确个数
        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)
        }

        // 修改被dalvik Hook的目标函数的jni参数为运行时实时计算
        // 原本函数可能并不是Native的,现在被偷偷改成了Native的,所以肯定不能使用这个域进行优化
        h->method->jniArgInfo = 0x80000000; // <--- also important
        if (h->debug_me) {

            log("noref %c\n", h->method->noRef)
            log("access %x\n", h->method->a)
        }

        // 保存被dalvik Hook的java层目标函数的原始函数属性标志值
        h->access_flags = h->method->a;
        // 修改被dalvik Hook的java层目标函数的为native层实现的jni函数
        h->method->a = h->method->a | h->af; // make method native

        if (h->debug_me)
            log("access %x\n", h->method->a)

        // 调用libdvm.so中的dvmUseJNIBridge函数来实现将method->nativeFunc域改成指向一个JNI桥跳转函数地址(dvmCallJNIMethod)
        // 并将method->insns域改成指向真正的jni函数代码即我们自定义实现的dalvik Hook函数代码首地址处
        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)

        // 到这里,java层目标函数的dalvik Hook实现完成
        return (void*)1;

    } else {

        // 查找被dalvik hook的java层目标函数失败的情况
        if (h->debug_me)
            log("could NOT patch %s\n", h->method_name)
    }

    return (void*)0;
}

在上面的分析中基本将ddi Hook框架下dalvik模式的java Hook的关键操作和修改说的很清楚了,但是有些细节的地方没有细说,具体的再回头看看前面提到的 Method结构体 的组成,到这一步基于dalvik虚拟机的java Hook就实现了。

9. dalvik虚拟机模式下,目标java函数被java Hook的步骤已经完成,下面来分析下 替换被java Hook目标函数 的native方法(自定义实现的jni函数)的实现。以被java Hook的 目标函数StringBuffer.toString() 为例子,替换java目标函数toString的native函数为 jni函数sb1_tostring 如下所示,为了调用被java Hook的目标函数toString的原始方法,先调用 dalvik_prepare 函数 取消对目标函数toString的dalvik Hook,然后通过反射调用原始的java层目标函数toString,然后再次调用 dalvik_postcall函数 对java层实现的目标函数进行再次 dalvik Hook 操作。

// patches
static void* sb1_tostring(JNIEnv *env, jobject obj)
{
    // 恢复还原被dalvik Hook的java层目标函数
    dalvik_prepare(&d, &sb1, env);

    // 调用被java Hook目标函数的原始函数
    void *res = (*env)->CallObjectMethod(env, obj, sb1.mid); 

    // 再次对java层实现的目标函数进行dalvik Hook操作
    dalvik_postcall(&d, &sb1);

    // 进行字符串的转换
    const char *s = (*env)->GetStringUTFChars(env, res, 0);
    if (s) {

        log("sb1.toString() = %s\n", s)
        (*env)->ReleaseStringUTFChars(env, res, s); 
    }

    return res;
}

10. dalvik_prepare 函数的实现如下,dalvik_prepare 函数的作用是 恢复还原被dalvik Hook的java层目标函数的信息结构体Method的成员变量 access_flags、insSize、registersSize、outsSize、jniArgInfo、insns 的原始值,从而实现被java Hook的目标函数dalvik Hook的取消和原始函数调用的恢复还原。

// 恢复还原被dalvik Hook的java层目标函数
int dalvik_prepare(struct dexstuff_t *dex, struct dalvik_hook_t *h, JNIEnv *env)
{

    // this seems to crash when hooking "constructors"
    // 判断需要被恢复还原的dalvik Hook目标函数是否存在
    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;

    }

    // 恢复被dalvik Hook的java层目标函数的原始寄存器数量
    h->method->insSize = h->iss;
    h->method->registersSize = h->rss;
    h->method->outsSize = h->oss;

    // 恢复被dalvik Hook的java层目标函数的原始函数类型属性
    h->method->a = h->access_flags;
    h->method->jniArgInfo = 0;
    // 恢复被dalvik Hook的java层目标函数的dalvik字节码指针
    h->method->insns = h->insns; 

    return 1;
}

11. dalvik_postcall函数的实现如下,dalvik_postcall函数的作用是通过再一次修改java目标函数的信息结构体Method的成员变量 access_flags、insSize、registersSize、outsSize、jniArgInfo、insns 、native_func 的值将一个java函数修改为native属性的自定义jni函数,从而实现对java目标函数的再次 dalvik Hook操作。

// 再次对java层实现的目标函数进行dalvik Hook操作
void dalvik_postcall(struct dexstuff_t *dex, struct dalvik_hook_t *h)
{

    // 修改被dalvik Hook的java层目标函数的寄存器数量值
    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)

    // 修改被dalvik Hook的java层目标函数的参数的计算方法为运行时实时计算
    h->method->jniArgInfo = 0x80000000;
    //log("noref %c\n", h->method->noRef)
    //log("access %x\n", h->method->a)

    // 修改被dalvik Hook的java层目标函数的类型属性为native
    h->access_flags = h->method->a;
    h->method->a = h->method->a | h->af;
    //log("access %x\n", h->method->a)

    // 修改被dalvik Hook的目标函数的insns为我们自定义的dalvik Hook函数的代码首地址
    // 修改nativeFunc为函数正确调用需要的jni跳转桥代码的地址
    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模式下java Hook框架ddi的编译和运行

java Hook框架ddi的源码下载地址:https://github.com/crmulliner/ddi

1. java Hook框架ddi源码的结构如下图所示,ddi框架的源码中提供了 2个dalvik虚拟机模式下 java Hook的例子,具体的实现都差不多,只是在java Hook替换函数(native 属性自定义的jni函数)的实现上稍有不同。
这里写图片描述

2. ddi框架的源码编译步骤如下:

#!/bin/sh

cd hijack/jni
ndk-build
cd ../..

cd dalvikhook/jni
ndk-build
cd ../..

cd examples
cd strmon/jni
ndk-build
cd ../..

3. ddi框架源码编译后的运行步骤如下:

# 拷贝需要的动态加载库文件到Android系统的/system/lib路径下
cd dalvikhook
cd jni
cd libs

adb pull /system/lib/libdl.so
adb pull /system/lib/libdvm.so

adb shell chmod 0777 /system/lib/libdl.so
adb shell chmod 0777 /system/lib/libdvm.so

#拷贝进程注入工具hijack到Android系统的/data/local/tmp路径下
adb push  adbi/hijack/libs/armeabi/hijack /data/local/tmp/
adb shell chmod 0777 /data/local/tmp/hijack

# 拷贝java Hook动态库文件libstrmon.so到Android系统的/data/local/tmp路径下
adb push  examples/strmon/libs/armeabi/libstrmon.so /data/local/tmp
adb shell chmod 0777 /data/local/tmp/libstrmon.so

# 进行dalvik模式下的java Hook操作(Run strmon)
adb shell
su
cd /data/local/tmp

# GET PID from com.android.contacts
ps | grep com.android.contacts

# 设置log日志的打印
>/data/local/tmp/strmon.log
chmod 777 /data/local/tmp/strmon.log

# 对进程com.android.contacts进行java Hook操作
./hijack -d -p PID -l /data/local/tmp/libstrmon.so

# 获取打印的log
cat strmon.log

4.ddi框架源码的编译和运行也可以参考官方的文档说明:https://github.com/crmulliner/ddi/blob/master/README.md



参考链接:
深入理解Android之Java虚拟机Dalvik
Dalvik虚拟机JNI方法的注册过程分析
Android平台下Dalvik层hook框架ddi的研究



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!