FastHook——遠(yuǎn)超YAHFA的優(yōu)異穩(wěn)定性

一、 概述

經(jīng)過實際項目大量測試驗證,F(xiàn)astHook表現(xiàn)出了遠(yuǎn)超YAHFA的優(yōu)異穩(wěn)定性。用戶反饋未出現(xiàn)Hook引發(fā)的穩(wěn)定性問題鹿榜、壓力測試也未發(fā)生Hook引發(fā)的穩(wěn)定問題。之所以FastHook擁有優(yōu)異的穩(wěn)定性锦爵,除了框架實現(xiàn)原理的優(yōu)越性之外舱殿,還得益于FastHook出色的細(xì)節(jié)處理
本文將通過FastHook實現(xiàn)原理優(yōu)越性與一些出色的細(xì)節(jié)處理來解釋為何FastHook擁有優(yōu)異的穩(wěn)定性险掀,最后對比YAHFA框架沪袭。

二、先天優(yōu)勢

如果你還未了解FastHook樟氢,請移步FastHook——一種高效穩(wěn)定冈绊、簡潔易用的Android Hook框架
FastHook相較YAHFA原理上最大的優(yōu)勢、也是最大的亮點便是:不需要備份原方法截型!不需要備份原方法痕檬!不需要備份原方法!
科學(xué)上有一個著名的“奧卡姆剃刀定律”十电,什么意思呢?如果一個現(xiàn)象有兩個或者多個不同的理論解釋,那么選最簡單的那個鹃骂。做Hook框架台盯,也可以用剃刀定律來做指導(dǎo):實現(xiàn)相同的功能,選對系統(tǒng)狀態(tài)改動最小的畏线。
“備份原方法”是一種隱患頗多的方式静盅,引發(fā)了諸如方法解析出錯、Moving GC空指針等問題寝殴。盡管其他框架通過一些手段來提高穩(wěn)定性蒿叠,比如保證方法不被再次解析、檢查Moving GC是否移動了原方法相關(guān)對象等蚣常,但是這些都不是理論安全的市咽,就像地上有個坑,你不去補上抵蚊,而是讓人不要去踩施绎。
反觀FastHook,Hook時對系統(tǒng)原有狀態(tài)的改變是最小的贞绳。

  1. Inline模式改變的僅是幾個字節(jié)的指令谷醉,因平臺而異,不篡改任何方法冈闭。
  2. EntryPoint模式替換了方法EntryPoint俱尼,但是原方法將強制為解釋執(zhí)行,也可等價的看為未做修改萎攒。

簡而言之遇八,F(xiàn)astHook就是用Hook方法hook原方法,原方法hook Forward方法來實現(xiàn)最小改動hook耍休。完美地從實現(xiàn)層面解決了YAHFA不能解決的問題押蚤,而且無需做一些其他操作,其他框架都需要一些其他的操作來提高穩(wěn)定性羹应,而FastHook不需要做任何其他處理揽碘,更簡潔、更優(yōu)雅园匹。

三雳刺、比YAHFA更出色的細(xì)節(jié)處理

3.1 JIT狀態(tài)檢查

如果你看過YAHFA代碼,你會發(fā)現(xiàn)沒有一個框架做了JIT狀態(tài)檢查裸违。JIT狀態(tài)檢查的目的是為了保證hook的安全性掖桦,但這也不是理論安全的,也無法做到理論安全供汛。這是為什么呢枪汪?

3.1.1 Inline模式

如果原方法未編譯則需要進行手動JIT編譯涌穆。那么問題來了,什么時候編譯才是安全的呢雀久。下面列舉出所有可能出現(xiàn)的情景:

  1. 原方法未進行JIT編譯宿稀,此時手動JIT編譯時安全的
  2. 原方法未進行JIT編譯,即將進入編譯等待隊列或已進入編譯等待隊列赖捌,此時手動JIT編譯是不安全的
  3. 原方法正在JIT編譯祝沸,此時手動JIT編譯是不安全的
  4. 原方法編譯完成,此時手動編譯是安全的

上述4中情景越庇,其中2罩锐、3是不安全的。如果要保證手動JIT編譯的安全性卤唉,必須做到以下兩點:

  1. 禁止JIT編譯涩惑,防止從1變化到2
  2. 能夠判斷2、3桑驱,當(dāng)處于2境氢、3狀態(tài)時,等待其變化到4

現(xiàn)在來看看FastHook到底是怎么處理的

int CheckJitState(JNIEnv *env, jclass clazz, jobject target_method) {
    void *art_method = (void *)(*env)->FromReflectedMethod(env, target_method);
    //添加kAccCompileDontBother碰纬,禁止JIT、AOT編譯
    AddArtMethodAccessFlag(art_method, kAccCompileDontBother);
    uint32_t hotness_count = GetArtMethodHotnessCount(art_method);
    if(hotness_count >= kHotMethodThreshold) {
        //hotness_count >= hot_threshold问芬,肯定就不是1了悦析,看看是2、3此衅、4中的哪一個
        long entry_point = (long)GetArtMethodEntryPoint(art_method);
        if((void *)entry_point == art_quick_to_interpreter_bridge_) {
            void *profiling = GetArtMethodProfilingInfo(art_method);
            void *save_entry_point = GetProfilingSaveEntryPoint(profiling);
            if(save_entry_point) {
                //JIT垃圾回收會改變方法EntryPoint强戴,雖然方法已經(jīng)編譯了,但是EntryPoint也可能是art_quick_to_interpreter_bridge
                return kCompile;
            }else {
                //JIT狀態(tài)保存在profiling中挡鞍,通過其來判斷是否是正在編譯骑歹,如果不是可能是正在等待或者已經(jīng)編譯失敗。
                bool being_compiled = GetProfilingCompileState(profiling);
                if(being_compiled) {
                    return kCompiling;
                }else {
                    return kCompilingOrFailed;
                }
            }
        }
        return kCompile;
    }else {
        //hotness_count < hot_threshold墨微,可能是1道媚,也可能是2,即將進入編譯等待隊列翘县,統(tǒng)一加一個增量最域,如果此時大于hot_threshold,就認(rèn)為是2锈麸,反之是1
        uint32_t assumed_hotness_count = hotness_count + kHotMethodMaxCount;
        if(assumed_hotness_count > kHotMethodThreshold) {
            return kCompiling;
        }
    }
    return kNone;
}
class ProfilingInfo {
 private:
  ProfilingInfo(ArtMethod* method, const std::vector<uint32_t>& entries);

  // Number of instructions we are profiling in the ArtMethod.
  const uint32_t number_of_inline_caches_;

  // Method this profiling info is for.
  // Not 'const' as JVMTI introduces obsolete methods that we implement by creating new ArtMethods.
  // See JitCodeCache::MoveObsoleteMethod.
  ArtMethod* method_;

  // Whether the ArtMethod is currently being compiled. This flag
  // is implicitly guarded by the JIT code cache lock.
  // TODO: Make the JIT code cache lock global.
  bool is_method_being_compiled_;
  bool is_osr_method_being_compiled_;

  // When the compiler inlines the method associated to this ProfilingInfo,
  // it updates this counter so that the GC does not try to clear the inline caches.
  uint16_t current_inline_uses_;

  // Entry point of the corresponding ArtMethod, while the JIT code cache
  // is poking for the liveness of compiled code.
  const void* saved_entry_point_;

  // Dynamically allocated array of size `number_of_inline_caches_`.
  InlineCache cache_[0];
};
  1. AddArtMethodAccessFlag(art_method, kAccCompileDontBother)镀脂,設(shè)置kAccCompileDontBother禁止JIT、AOT忘伞。防止1變化到2薄翅。
  2. 如果hotness_count > hot_threshold沙兰,這時肯定就不是1了,還需要判斷是2翘魄、3鼎天、4中哪一個。
  3. 通過判斷entry point是否為解釋執(zhí)行入口來判斷是否是4熟丸,因為entry point不是解釋執(zhí)行入口肯定不會是2和3训措。
  4. 這里有個關(guān)鍵點一定要注意,即使JIT編譯后entry point也有可能為解釋執(zhí)行入口光羞,因為JIT垃圾回收會將entry point設(shè)置為解釋執(zhí)行入口绩鸣,將實際入口保存在save_entry_point。如果save_entry_point不為空纱兑,那證明已經(jīng)編譯過了呀闻。
  5. 怎么判斷2、3呢潜慎?每個方法都有一個profiling info捡多,保存一些運行過程信息和JIT編譯信息,其中就有是否在JIT編譯的信息铐炫。如果為true垒手,則為3,如果為false倒信,則為2(這里也可能是編譯失敗了的科贬,為了簡便都做2看待)
  6. 如果hotness_count < hot_threshold,能說明一定是1嗎鳖悠?答案是不能榜掌,也有可能是2。這是為什么呢乘综?有一種罕見的情況憎账,當(dāng)我們檢查狀態(tài)時,hotness_count還未執(zhí)行到更新的代碼卡辰,而當(dāng)其更新之后大于hot_threshold胞皱,那么實際就是2。因此假設(shè)hotness_count會更新九妈,給一個增量(理論上給不了準(zhǔn)確的數(shù)值朴恳,因為其增量受權(quán)重影響,也可能是批量處理的增量允蚣,因此這不是理論安全的)于颖,這里給一個比較大的值(50),如果此時大于hot_threshold嚷兔,就認(rèn)為是2(這個也不是完全準(zhǔn)確的森渐,因為可能hotness_count根本不會更新)做入。

3.1.2 小結(jié)

  1. hook之前先做JIT狀態(tài)檢查,如果安全就立即hook同衣,反之放入一個異步隊列延遲hook
  2. 上述分析可知竟块,該檢查也不是絕對安全的,但是已經(jīng)將出現(xiàn)問題的場景縮小到一個可以忽略不計的范圍耐齐。
  3. EntrypPoint替換模式的檢查與Inline模式一致浪秘,不做重復(fù)分析

3.2 判斷方法是否需要編譯

如果只是簡單用entry point與解釋入口比較來判斷埠况,通過3.1的分析可知這是不完備的耸携。
JIT垃圾回收會改變entry point為解釋入口,必須做進一步判斷是否為JIT編譯方法辕翰。FastHook的做法很簡單夺衍,判斷hotness_count是否小于hot_threshold,如果其小于hot_threshold喜命,那肯定還未被JIT編譯沟沙,因此可以判定其需要進行手動JIT編譯
并且壁榕,這一步是在JIT檢查成功基礎(chǔ)上進行的矛紫,可以不用擔(dān)心JIT狀態(tài)的影響。

bool IsCompiled(JNIEnv *env, jclass clazz, jobject method) {
    bool ret = false;
    void *art_method = (void *)(*env)->FromReflectedMethod(env, method);
    void *method_entry = (void *)ReadPointer((unsigned char *)art_method + kArtMethodQuickCodeOffset);
    int hotness_count = GetArtMethodHotnessCount(art_method);
   if(method_entry != art_quick_to_interpreter_bridge_)
        ret = true;
    if(!ret && hotness_count >= kHotMethodThreshold)
        ret = true;
    return ret;
}

3.3 線程狀態(tài)恢復(fù)

當(dāng)一個java方法進入JNI時牌里,線程狀態(tài)由runnable狀態(tài)變?yōu)閚ative狀態(tài)颊咬,返回java前恢復(fù)為runable狀態(tài)。而JIT編譯方法會將參數(shù)thread的狀態(tài)轉(zhuǎn)變?yōu)閞unnable狀態(tài)二庵。
最開始在手動JIT編譯方法時不做其他處理。但是后來項目上有反饋缓呛,有概率出現(xiàn)crash催享,出現(xiàn)的位置正好是編譯完成后返回java的地方,異常原因是線程狀態(tài)錯誤哟绊。
FastHook之前的解決方案是:新建native線程用于JIT編譯因妙,避免當(dāng)前線程編譯。這時出現(xiàn)了新的問題票髓,如何獲取native線程的thread對象攀涵?
通過研究android代碼發(fā)現(xiàn),art獲取線程thread對象是通過TLS來獲取的洽沟,thread存儲在TLS固定位置以故。但實際上,這種方案雖然解決了crash的問題裆操,但也導(dǎo)致了新的問題:線程錯誤地等待怒详。
究其緣由炉媒,都是線程狀態(tài)異常引起的,因此根治的方法便是恢復(fù)線程狀態(tài)昆烁。通過研究Thread代碼發(fā)現(xiàn)吊骤,線程狀態(tài)是一個union結(jié)構(gòu)體StateAndFlags,保存在thread對象里静尼,因此可以通過偏移的方式來訪問白粉。

static inline void *CurrentThread() {
    return __get_tls()[kTLSSlotArtThreadSelf];
}
#if defined(__aarch64__)
# define __get_tls() ({ void** __val; __asm__("mrs %0, tpidr_el0" : "=r"(__val)); __val; })
#elif defined(__arm__)
# define __get_tls() ({ void** __val; __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(__val)); __val; })
#endif
class Thread {
  union PACKED(4) StateAndFlags {
    struct PACKED(4) {
      volatile uint16_t flags;
      volatile uint16_t state;
    } as_struct;
    AtomicInteger as_atomic_int;
    volatile int32_t as_int;
  };
struct PACKED(4) tls_32bit_sized_values {
    typedef uint32_t bool32_t;
    union StateAndFlags state_and_flags;
    int suspend_count GUARDED_BY(Locks::thread_suspend_count_lock_);
    int debug_suspend_count GUARDED_BY(Locks::thread_suspend_count_lock_);
    uint32_t thin_lock_thread_id;
    uint32_t tid;
    const bool32_t daemon;
    bool32_t throwing_OutOfMemoryError;
    uint32_t no_thread_suspension;
    uint32_t thread_exit_check_count;
    bool32_t handling_signal_;
    bool32_t is_transitioning_to_runnable;
    bool32_t ready_for_debug_invoke;
    bool32_t debug_method_entry_;
    bool32_t is_gc_marking;
    Atomic<bool32_t> interrupted;
    bool32_t weak_ref_access_enabled;
    uint32_t disable_thread_flip_count;
    int user_code_suspend_count GUARDED_BY(Locks::thread_suspend_count_lock_);
  } tls32_;
bool CompileMethod(JNIEnv *env, jclass clazz, jobject method) {
    bool ret = false;

    void *art_method = (void *)(*env)->FromReflectedMethod(env, method);
    void *thread = CurrentThread();
    int old_flag_and_state = ReadInt32(thread);

    ret = jit_compile_method_(jit_compiler_handle_, art_method, thread, false);
    memcpy(thread,&old_flag_and_state,4);

    return ret;
}

3.4 指令檢查

Inline模式下需要注入代碼,那么就必須確保被覆蓋的指令不包含pc相關(guān)的指令鼠渺。
這是為什么呢鸭巴?pc寄存器存儲的是當(dāng)前執(zhí)行的指令,如果以pc寄存器來做尋址就跟當(dāng)前地址息息相關(guān)了系冗,如果我們覆蓋的指令包含pc相關(guān)的指令奕扣,那么尋址將出錯。
需要注意的是掌敬,Thumb2有16位和32位兩種指令惯豆,因此對于Thumb2指令集還需額外判斷指令類型。

static inline bool IsThumb32(uint16_t inst, bool little_end) {
    if(little_end) {
        return ((inst & 0xe000) == 0xe000 && (inst & 0x1800) != 0x0000);
    }
    return ((inst & 0x00e0) == 0x00e0 && (inst & 0x0018) != 0x0000);
}
static inline bool HasThumb16PcRelatedInst(uint16_t inst) {
    uint16_t mask_b1 = 0xf000;
    uint16_t op_b1 = 0xd000;
    uint16_t mask_b2_adr_ldr = 0xf800;
    uint16_t op_b2 = 0xe000;
    uint16_t op_adr = 0xa000;
    uint16_t op_ldr = 0x4800;
    uint16_t mask_bx = 0xfff8;
    uint16_t op_bx = 0x4778;
    uint16_t mask_add_mov = 0xff78;
    uint16_t op_add = 0x4478;
    uint16_t op_mov = 0x4678;
    uint16_t mask_cb = 0xf500;
    uint16_t op_cb = 0xb100;

    if((inst & mask_b1) == op_b1)
        return true;
    if((inst * mask_b2_adr_ldr) == op_b2 || (inst * mask_b2_adr_ldr) == op_adr || (inst * mask_b2_adr_ldr) == op_ldr)
        return true;
    if((inst & mask_bx) == op_bx)
        return true;
    if((inst & mask_add_mov) == op_add || (inst & mask_add_mov) == op_mov)
        return true;
    if((inst & mask_cb) == op_cb)
        return true;
    return false;
}
static inline bool HasThumb32PcRelatedInst(uint32_t inst) {
    uint32_t mask_b = 0xf800d000;
    uint32_t op_blx = 0xf000c000;
    uint32_t op_bl = 0xf000d000;
    uint32_t op_b1 = 0xf0008000;
    uint32_t op_b2 = 0xf0009000;
    uint32_t mask_adr = 0xfbff8000;
    uint32_t op_adr1 = 0xf2af0000;
    uint32_t op_adr2 = 0xf20f0000;
    uint32_t mask_ldr = 0xff7f0000;
    uint32_t op_ldr = 0xf85f0000;
    uint32_t mask_tb = 0xffff00f0;
    uint32_t op_tbb = 0xe8df0000;
    uint32_t op_tbh = 0xe8df0010;

    if((inst & mask_b) == op_blx || (inst & mask_b) == op_bl || (inst & mask_b) == op_b1 || (inst & mask_b) == op_b2)
        return true;
    if((inst & mask_adr) == op_adr1 || (inst & mask_adr) == op_adr2)
        return true;
    if((inst & mask_ldr) == op_ldr)
        return true;
    if((inst & mask_tb) == op_tbb || (inst & mask_tb) == op_tbh)
        return true;
    return false;
}
static inline bool HasArm64PcRelatedInst(uint32_t inst) {

    uint32_t mask_b = 0xfc000000;
    uint32_t op_b = 0x14000000;
    uint32_t op_bl = 0x94000000;
    uint32_t mask_bc = 0xff000010;
    uint32_t op_bc = 0x54000000;
    uint32_t mask_cb = 0x7f000000;
    uint32_t op_cbz = 0x34000000;
    uint32_t op_cbnz = 0x35000000;
    uint32_t mask_tb = 0x7f000000;
    uint32_t op_tbz = 0x36000000;
    uint32_t op_tbnz = 0x37000000;
    uint32_t mask_ldr = 0xbf000000;
    uint32_t op_ldr = 0x18000000;
    uint32_t mask_adr = 0x9f000000;
    uint32_t op_adr = 0x10000000;
    uint32_t op_adrp = 0x90000000;

    if((inst & mask_b) == op_b || (inst & mask_b) == op_bl)
        return true;
    if((inst & mask_bc) == op_bc)
        return true;
    if((inst & mask_cb) == op_cbz || (inst & mask_cb) == op_cbnz)
        return true;
    if((inst & mask_tb) == op_tbz || (inst & mask_tb) == op_tbnz)
        return true;
    if((inst & mask_ldr) == op_ldr)
        return true;
    if((inst & mask_adr) == op_adr || (inst & mask_adr) == op_adrp)
        return true;
    return false;
}

主要是幾類指令:

  1. 分支跳轉(zhuǎn)指令
  2. 比較分支指令
  3. 條件分支指令
  4. load指令

而Thumb2需要特別注意奔害,因為其有16位和32位兩種模式楷兽,而跳轉(zhuǎn)指令長度是8字節(jié),如果固定復(fù)制8字節(jié)华临,有可能會把指令截斷芯杀,例如4-2-4,最后4字節(jié)指令將會被截斷雅潭,因此需要做判斷揭厚,以確定需要復(fù)制8字節(jié)還是10字節(jié)

int original_prologue_len = 0;
    while(original_prologue_len < jump_trampoline_len) {
        if(IsThumb32(ReadInt16((unsigned char *)target_code + original_prologue_len),IsLittleEnd())) {
            original_prologue_len += 4;
        }else {
            original_prologue_len += 2;
        }
    }

3.5 指令注入

Inline模式下,需要向目標(biāo)方法代碼段注入一段跳轉(zhuǎn)指令扶供,而代碼段是不可寫筛圆。一般解決方案是使用mprotect修改訪問權(quán)限
而從實際項目測試來看椿浓,mprotect可能是無效的太援。mprotect執(zhí)行成功了,但是還是出現(xiàn)了SEGV_ACCERR扳碍。
FastHook的解決方案是先捕獲出錯信號提岔,再使用mprotect修改訪問權(quán)限。如果修改無效笋敞,則一直會修改直到生效為止碱蒙。指令注入后恢復(fù)默認(rèn)信號處理。捕獲信號處理之后夯巷,再無crash的反饋振亮。

void SignalHandle(int signal, siginfo_t *info, void *reserved) {
    ucontext_t* context = (ucontext_t*)reserved;
    void *addr = (void *)context->uc_mcontext.fault_address;

    if(sigaction_info_->addr == addr) {
        void *target_code = sigaction_info_->addr;
        int len = sigaction_info_->len;
        long page_size = sysconf(_SC_PAGESIZE);
        unsigned alignment = (unsigned)((unsigned long long)target_code % page_size);
        int ret = mprotect((void *) (target_code - alignment), (size_t) (alignment + len),
                           PROT_READ | PROT_WRITE | PROT_EXEC);
    }
}
    sigaction_info_->addr = target_code;
    sigaction_info_->len = original_prologue_len;
    if(current_handler_ == NULL) {
        default_handler_ = (struct sigaction *)malloc(sizeof(struct sigaction));
        current_handler_ = (struct sigaction *)malloc(sizeof(struct sigaction));
        memset(default_handler_, 0, sizeof(sigaction));
        memset(current_handler_, 0, sizeof(sigaction));
        current_handler_->sa_sigaction = SignalHandle;
        current_handler_->sa_flags = SA_SIGINFO;
        sigaction(SIGSEGV, current_handler_, default_handler_);
    }else {
        sigaction(SIGSEGV, current_handler_, NULL);
    }

    memcpy(target_code, jump_trampoline, jump_trampoline_len);

    sigaction_info_->addr = NULL;
    sigaction_info_->len = 0;
    sigaction(SIGSEGV, default_handler_, NULL);

3.6 注入安全

在獲得寫權(quán)限之后巧还,注入的時候必須保證沒有其他線程同時讀需要注入的區(qū)域,不然將導(dǎo)致未知錯誤坊秸。
可以利用art暫停所用線程和恢復(fù)所有線程的接口來實現(xiàn)麸祷。FastHook并沒有采用這種方式,stop the world這種方式太重了褒搔,對性能有損耗阶牍。
FastHook是怎么做的呢?很簡單星瘾,強制需要注入的方法解釋執(zhí)行走孽,注入完成后恢復(fù)。即保證了注入安全琳状,也沒有任何性能損失磕瓷。

memcpy((unsigned char *) art_target_method + kArtMethodQuickCodeOffset,&art_quick_to_interpreter_bridge_,pointer_size_);
memcpy(target_code, jump_trampoline, jump_trampoline_len);
memcpy((unsigned char *) art_target_method + kArtMethodQuickCodeOffset,&target_entry,pointer_size_);

3.7 EntryPoint替換安全

EntryPoint替換模式要求原方法以解釋模式執(zhí)行,而JIT垃圾回收會更改方法entry point為解釋執(zhí)行入口念逞,當(dāng)方法即將進入解釋執(zhí)行時會重新設(shè)置為原來的入口困食,這會導(dǎo)致什么問題呢?
java方法有兩種執(zhí)行模式翎承,一種執(zhí)行dex字節(jié)碼硕盹,一種執(zhí)行機器碼,art因此需要知道機器碼與dex字節(jié)碼的映射關(guān)系叨咖,例如執(zhí)行一條機器碼瘩例,它對應(yīng)哪一條dex字節(jié)碼。而這些映射需要方法entry point作為基址來計算甸各,此時entry point已經(jīng)被替換垛贤,會得出錯誤的結(jié)果
因此趣倾,如果監(jiān)測到上述情況聘惦,需要修改save_entry_point為解釋執(zhí)行入口,防止執(zhí)行JIT編譯的機器碼誊酌。

if(art_forward_method) {
        memcpy((unsigned char *) target_trampoline + hook_trampoline_target_index, &art_target_method, pointer_size_);
        memcpy((unsigned char *) target_trampoline + target_trampoline_target_entry_index, &target_entry, pointer_size_);
        if(kTLSSlotArtThreadSelf) {
            uint32_t hotness_count = GetArtMethodHotnessCount(art_target_method);
            if(hotness_count >= kHotMethodThreshold) {
                void *profiling = GetArtMethodProfilingInfo(art_target_method);
                void *save_entry_point = GetProfilingSaveEntryPoint(profiling);
                if(save_entry_point) {
                    SetProfilingSaveEntryPoint(profiling,art_quick_to_interpreter_bridge_);
                }
            }
        }
    }

四部凑、與其他框架比較

4.1 YAHFA

框架 備份原方法 性能 JIT狀態(tài)檢查 EntryPoint檢查(JIT) 線程狀態(tài)恢復(fù) 指令檢查 mprotect失效處理 注入安全 防止內(nèi)聯(lián) 防止backup/forword內(nèi)聯(lián)
YAHFA - - - - -
FastHook 是(高效) JIT內(nèi)聯(lián)

4.4 小結(jié)

從上述對比可以看出露乏,F(xiàn)astHook與YAHFA的本質(zhì)區(qū)別是不備份原方法碧浊,在細(xì)節(jié)上的處理也比其他框架要嚴(yán)謹(jǐn)高效瘟仿,YAHFA在細(xì)節(jié)處理上都有所欠缺箱锐。

五、結(jié)語

由于項目原因劳较,主要維護arm平臺驹止,其他平臺暫時不支持浩聋,后續(xù)再計劃加入,目前主要關(guān)注arm平臺的穩(wěn)定性臊恋。如果有興趣衣洁,對穩(wěn)定性有要求的朋友,歡迎使用抖仅,本項目長期維護坊夫。

FastHook系列

FastHook——一種高效穩(wěn)定、簡潔易用的Android Hook框架
FastHook——巧妙利用動態(tài)代理實現(xiàn)非侵入式AOP
如何使用FastHook免root hook微信
FastHook——實現(xiàn).dynsym段和.symtab段符號查詢

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撤卢,一起剝皮案震驚了整個濱河市环凿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌放吩,老刑警劉巖智听,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渡紫,居然都是意外死亡到推,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門腻惠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來环肘,“玉大人,你說我怎么就攤上這事集灌』诒ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵欣喧,是天一觀的道長腌零。 經(jīng)常有香客問我,道長唆阿,這世上最難降的妖魔是什么益涧? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮驯鳖,結(jié)果婚禮上闲询,老公的妹妹穿的比我還像新娘。我一直安慰自己浅辙,他們只是感情好扭弧,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著记舆,像睡著了一般鸽捻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天御蒲,我揣著相機與錄音衣赶,去河邊找鬼。 笑死厚满,一個胖子當(dāng)著我的面吹牛府瞄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碘箍,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摘能,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了敲街?” 一聲冷哼從身側(cè)響起团搞,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎多艇,沒想到半個月后逻恐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡峻黍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年复隆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆涩。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡挽拂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骨饿,到底是詐尸還是另有隱情亏栈,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布宏赘,位于F島的核電站绒北,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏察署。R本人自食惡果不足惜闷游,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贴汪。 院中可真熱鬧脐往,春花似錦、人聲如沸扳埂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聂喇。三九已至辖源,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間希太,已是汗流浹背克饶。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留誊辉,地道東北人矾湃。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像堕澄,于是被迫代替她去往敵國和親邀跃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容