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

本文博客地址: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的研究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章