首页
论坛
课程
招聘
[原创]FastHook——远超YAHFA的优异稳定性
2019-3-22 13:12 12951

[原创]FastHook——远超YAHFA的优异稳定性

2019-3-22 13:12
12951

一、 概述

经过实际项目大量测试验证,FastHook表现出了远超YAHFA的优异稳定性。用户反馈未出现Hook引发的稳定性问题、压力测试也未发生Hook引发的稳定问题。之所以FastHook拥有优异的稳定性,除了框架实现原理的优越性之外,还得益于FastHook出色的细节处理
本文将通过FastHook实现原理优越性与一些出色的细节处理来解释为何FastHook拥有优异的稳定性,最后对比其YAHFA。

二、先天优势

如果你还未了解FastHook,请移步FastHook——一种高效稳定、简洁易用的Android Hook框架
FastHook相较YAHFA原理上最大的优势、也是最大的亮点便是:不需要备份原方法!不需要备份原方法!不需要备份原方法!
科学上有一个著名的“奥卡姆剃刀定律”,什么意思呢?如果一个现象有两个或者多个不同的理论解释,那么选最简单的那个。做Hook框架,也可以用剃刀定律来做指导:实现相同的功能,选对系统状态改动最小的
“备份原方法”是一种隐患颇多的方式,引发了诸如方法解析出错、Moving GC空指针等问题。尽管其他框架通过一些手段来提高稳定性,比如保证方法不被再次解析、检查Moving GC是否移动了原方法相关对象等,但是这些都不是理论安全的,就像地上有个坑,你不去补上,而是让人不要去踩
反观FastHook,Hook时对系统原有状态的改变是最小的。

  1. Inline模式改变的仅是几个字节的指令,因平台而异,不篡改任何方法。
  2. EntryPoint模式替换了方法EntryPoint,但是原方法将强制为解释执行,也可等价的看为未做修改。

简而言之,FastHook就是用Hook方法hook原方法,原方法hook Forward方法来实现最小改动hook。完美地从实现层面解决了YAHFA不能解决的问题,而且无需做一些其他操作,YAHFA都需要一些其他的操作来提高稳定性,而FastHook不需要做任何其他处理,更简洁、更优雅

三、比YAHFA更出色的细节处理

3.1 JIT状态检查

如果你看YAHFA代码,你会发现其未做JIT状态检查。JIT状态检查的目的是为了保证hook的安全性,但这也不是理论安全的,也无法做到理论安全。这是为什么呢?

3.1.1 Inline模式

如果原方法未编译则需要进行手动JIT编译。那么问题来了,什么时候编译才是安全的呢。下面列举出所有可能出现的情景:

  1. 原方法未进行JIT编译,此时手动JIT编译时安全的
  2. 原方法未进行JIT编译,即将进入编译等待队列或已进入编译等待队列,此时手动JIT编译是不安全的
  3. 原方法正在JIT编译,此时手动JIT编译是不安全的
  4. 原方法编译完成,此时手动编译是安全的

上述4中情景,其中2、3是不安全的。如果要保证手动JIT编译的安全性,必须做到以下两点:

  1. 禁止JIT编译,防止从1变化到2
  2. 能够判断2、3,当处于2、3状态时,等待其变化到4

现在来看看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,虽然方法已经编译了,但是EntryPoint也可能是art_quick_to_interpreter_bridge
                return kCompile;
            }else {
                //JIT状态保存在profiling中,通过其来判断是否是正在编译,如果不是可能是正在等待或者已经编译失败。
                bool being_compiled = GetProfilingCompileState(profiling);
                if(being_compiled) {
                    return kCompiling;
                }else {
                    return kCompilingOrFailed;
                }
            }
        }
        return kCompile;
    }else {
        //hotness_count < hot_threshold,可能是1,也可能是2,即将进入编译等待队列,统一加一个增量,如果此时大于hot_threshold,就认为是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),设置kAccCompileDontBother禁止JIT、AOT。防止1变化到2
  2. 如果hotness_count > hot_threshold,这时肯定就不是1了,还需要判断是2、3、4中哪一个。
  3. 通过判断entry point是否为解释执行入口来判断是否是4,因为entry point不是解释执行入口肯定不会是2和3
  4. 这里有个关键点一定要注意,即使JIT编译后entry point也有可能为解释执行入口,因为JIT垃圾回收会将entry point设置为解释执行入口,将实际入口保存在save_entry_point。如果save_entry_point不为空,那证明已经编译过了。
  5. 怎么判断2、3呢?每个方法都有一个profiling info,保存一些运行过程信息和JIT编译信息,其中就有是否在JIT编译的信息。如果为true,则为3,如果为false,则为2(这里也可能是编译失败了的,为了简便都做2看待)
  6. 如果hotness_count < hot_threshold,能说明一定是1吗?答案是不能,也有可能是2。这是为什么呢?有一种罕见的情况,当我们检查状态时,hotness_count还未执行到更新的代码,而当其更新之后大于hot_threshold,那么实际就是2。因此假设hotness_count会更新,给一个增量(理论上给不了准确的数值,因为其增量受权重影响,也可能是批量处理的增量,因此这不是理论安全的),这里给一个比较大的值(50),如果此时大于hot_threshold,就认为是2(这个也不是完全准确的,因为可能hotness_count根本不会更新)。

3.1.2 小结

  1. hook之前先做JIT状态检查,如果安全就立即hook,反之放入一个异步队列延迟hook
  2. 上述分析可知,该检查也不是绝对安全的,但是已经将出现问题的场景缩小到一个可以忽略不计的范围
  3. EntrypPoint替换模式的检查与Inline模式一致,不做重复分析

3.2 判断方法是否需要编译

如果只是简单用entry point与解释入口比较来判断,通过3.1的分析可知这是不完备的
JIT垃圾回收会改变entry point为解释入口,必须做进一步判断是否为JIT编译方法。FastHook的做法很简单,判断hotness_count是否小于hot_threshold,如果其小于hot_threshold,那肯定还未被JIT编译,因此可以判定其需要进行手动JIT编译
并且,这一步是在JIT检查成功基础上进行的,可以不用担心JIT状态的影响。

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 线程状态恢复

当一个java方法进入JNI时,线程状态由runnable状态变为native状态,返回java前恢复为runable状态。而JIT编译方法会将参数thread的状态转变为runnable状态
最开始FastHook在手动JIT编译方法时不做其他处理。但是后来项目上有反馈,有概率出现crash,出现的位置正好是编译完成后返回java的地方,异常原因是线程状态错误。
FastHook之前的解决方案是:新建native线程用于JIT编译,避免当前线程编译。这时出现了新的问题,如何获取native线程的thread对象?
通过研究android代码发现,art获取线程thread对象是通过TLS来获取的,thread存储在TLS固定位置。但实际上,这种方案虽然解决了crash的问题,但也导致了新的问题:线程错误地等待
究其缘由,都是线程状态异常引起的,因此根治的方法便是恢复线程状态。通过研究Thread代码发现,线程状态是一个union结构体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相关的指令
这是为什么呢?pc寄存器存储的是当前执行的指令,如果以pc寄存器来做寻址就跟当前地址息息相关了,如果我们覆盖的指令包含pc相关的指令,那么寻址将出错。
需要注意的是,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. 分支跳转指令
  2. 比较分支指令
  3. 条件分支指令
  4. load指令

而Thumb2需要特别注意,因为其有16位和32位两种模式,而跳转指令长度是8字节,如果固定复制8字节,有可能会把指令截断,例如4-2-4,最后4字节指令将会被截断,因此需要做判断,以确定需要复制8字节还是10字节

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模式下,需要向目标方法代码段注入一段跳转指令,而代码段是不可写。一般解决方案是使用mprotect修改访问权限
而从实际项目测试来看,mprotect可能是无效的。mprotect执行成功了,但是还是出现了SEGV_ACCERR
FastHook的解决方案是先捕获出错信号,再使用mprotect修改访问权限。如果修改无效,则一直会修改直到生效为止。指令注入后恢复默认信号处理。捕获信号处理之后,再无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 注入安全

在获得写权限之后,注入的时候必须保证没有其他线程同时读需要注入的区域,不然将导致未知错误。
可以利用art暂停所用线程和恢复所有线程的接口来实现。FastHook并没有采用这种方式,stop the world这种方式太重了,对性能有损耗
FastHook是怎么做的呢?很简单,强制需要注入的方法解释执行,注入完成后恢复。即保证了注入安全,也没有任何性能损失

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替换模式要求原方法以解释模式执行,而JIT垃圾回收会更改方法entry point为解释执行入口,当方法即将进入解释执行时会重新设置为原来的入口,这会导致什么问题呢?
java方法有两种执行模式,一种执行dex字节码,一种执行机器码,art因此需要知道机器码与dex字节码的映射关系,例如执行一条机器码,它对应哪一条dex字节码。而这些映射需要方法entry point作为基址来计算,此时entry point已经被替换,会得出错误的结果
因此,如果监测到上述情况,需要修改save_entry_point为解释执行入口,防止执行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状态检查 EntryPoint检查(JIT) 线程状态恢复 指令检查 mprotect失效处理 注入安全 防止内联 防止backup/forword内联
YAHFA - - - - -
FastHook 是(高效) JIT内联

4.2 小结

从上述对比可以看出,FastHook与YAHFA的本质区别是不备份原方法,在细节上的处理也比其他框架要严谨高效其他框架在细节处理上都有所欠缺

五、结语

由于项目原因,主要维护arm平台,其他平台暂时不支持,后续再计划加入,目前主要关注arm平台的稳定性。如果有兴趣,对稳定性有要求的朋友,欢迎使用,本项目长期维护

六、参考

FastHook——一种高效稳定、简洁易用的Android Hook框架
FastHookhttps://github.com/turing-technician/FastHook


[公告]春风十里不如你,看雪团队诚邀你的加入!

最后于 2019-3-24 10:02 被图灵技师编辑 ,原因: 为了避免一些不必要的非议,同时追求严谨性,特做出修改
收藏
点赞3
打赏
分享
最新回复 (17)
雪    币: 1790
活跃值: 活跃值 (720)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Vn小帆 活跃值 2019-3-22 13:24
2
0
so 也可以hook?
雪    币: 2248
活跃值: 活跃值 (25)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
图灵技师 活跃值 1 2019-3-22 14:06
3
1
Vn小帆 so 也可以hook?
不可以,这是art hook框架,仅限java方法
雪    币: 30
活跃值: 活跃值 (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
尕可 活跃值 2019-3-22 14:48
4
0
支持一个。
雪    币: 76
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mingxuan三千 活跃值 2019-3-22 15:14
5
0
666
雪    币: 347
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Wika 活跃值 2019-3-22 18:34
6
1
一直用frida,这几天研究这个框架试试,感觉不错
雪    币: 210
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
维给力 活跃值 2019-3-22 20:38
7
0
多谢分享。。。
雪    币: 31
活跃值: 活跃值 (256)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
twsxtd 活跃值 2019-3-23 22:27
8
0
已经不符合语境了,为避免误解删除。
最后于 2019-3-25 10:39 被twsxtd编辑 ,原因: 无
雪    币: 2248
活跃值: 活跃值 (25)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
图灵技师 活跃值 1 2019-3-24 09:53
9
1
twsxtd 你的思路确实有一些优秀和闪光的地方,这是必须承认的。但是,像yahfa / epic / frida / AndHook /&nbsp;DInlineHook / whale / sandho ...
首先,感谢你的批评指点。
第二,我必须承认这篇文章有些不严谨的地方,FastHook主要是解决YAHFA存在的不足,其他框架两个框架我用得少,epic我没用过商业版的,不知道,开源版的经常出现一些莫名其妙的问题,所以我弃用了。SandHook我没用过,但是这个框架跟FastHook很相似,它解决的问题,FastHook也解决了,所以我直接拿来横向对比了。文章说的远超其他框架其实说的是这三个框架,这是我的问题,我表达不严谨,我稍后更正,为了严谨,其他框架统一更正为YAHFA。
第三、你说的“在你的框架里面看起来很重要的问题在他们那里或许压根就不存在。用自己的维度去评判,这个我不敢苟同“,我也不敢苟同。首先,备份原方法一定会有方法解析跟GC问题,这是毫无疑问的。我也说了其他框架(YAHFA、epic(开源版)、SandHook)会有自己解决的方案,但我的观点是“规避问题不代表解决问题”,指令检查是inline hook必不可少的手段,文章说了只有SandHook做了检查(epic(开源版没有)),这也没什么问题。至于JIT检查、是否编译判断、JIT编译线程状态、mprotect失效,注入安全,这些是我项目上遇到的问题,如果其他框架没有问题是不是是问题还未暴露?这个是争议点,但我保留观点。
最后,我声明一下,我写这些文章的原因。我之前用的YAHFA框架来做项目,它存在一些问题,我觉得我解决了这写问题,为了不那么无耻,我觉得我应该开源出来。这就是一个个人项目,我没有团队,没有商用、没有交流群,也没有因为开源了一个东西就涨工资了。
开源旨在交流进步,不是为了显得谁谁更牛逼。
雪    币: 31
活跃值: 活跃值 (256)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
twsxtd 活跃值 2019-3-24 13:29
10
0
图灵技师 首先,感谢你的批评指点。 第二,我必须承认这篇文章有些不严谨的地方,FastHook主要是解决YAHFA存在的不足,其他框架两个框架我用得少,epic我没用过商业版的,不知道,开源版的经常出现一些莫 ...
1. 你用“奥卡姆剃刀定律” 审视一下FastHook 对除了备份原方法之外的处理手段。因为你不备份原方法,所以不会遇到resolve和gc的问题,那么别人可能因为不做xxx,就不会遇到你所说的其他问题。
2. 闭源 != 无耻。如何处理源码是个人选择,无可厚非。
雪    币: 2248
活跃值: 活跃值 (25)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
图灵技师 活跃值 1 2019-3-24 13:47
11
0
twsxtd 1. 你用“奥卡姆剃刀定律” 审视一下FastHook 对除了备份原方法之外的处理手段。因为你不备份原方法,所以不会遇到resolve和gc的问题,那么别人可能因为不做xxx,就不会遇到你所说的其他问 ...
1.“至于JIT检查、是否编译判断、JIT编译线程状态、mprotect失效,注入安全,这些是我项目上遇到的问题,如果其他框架没有问题是不是是问题还未暴露?这个是争议点,但我保留观点”这是我的观点,这是建立在相似性上做的一些判断。如果是Inline hook,那么一定会手动编译方法,一定会注入指令,我没有说其他框架一定会存在问题。我之前不严谨的地方已经纠正过来了,而这些是我在实际工作遇到的问题,依据这个我保留观点。
2.澄清一下,我并有说闭源等于无耻,也没有这方面的思想。我想表达意思是,更宽容地看待开源项目,我水平有限,写的东西不一定对,但不代表我不能发表意见,如果我错了,大家指正出来,我立即改正,像你之前批评不严谨的地方,我已经改正过来。而你说的其他问题,我觉得目前的论据还不足以说服我,所以我暂时保留观点。
雪    币: 514
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
皮皮豪 活跃值 2019-3-24 18:38
12
0
感谢大佬
雪    币: 236
活跃值: 活跃值 (59)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
ckis 活跃值 2019-3-25 10:12
13
1
你的code水平明显大于文笔 何必在这里用你的短处去做世俗之争?

不管谁更牛 开源行为促进了行业发展 值得敬佩
雪    币: 76
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mingxuan三千 活跃值 2019-3-25 10:18
14
0
开源支持
雪    币: 551
活跃值: 活跃值 (287)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
skyun 活跃值 3 2019-3-25 10:43
15
0
促进了行业发展 值得敬佩
雪    币: 3540
活跃值: 活跃值 (59)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
liux 活跃值 2019-4-2 11:12
16
0
这方面能讲得透彻的资料确实并不好找,而且每个人的技术水平和理解能力都不一样,所以一份比较好的开源软件源码总是非常有价值
雪    币: 34
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vurtneye 活跃值 2019-4-10 09:33
17
0
能开源都是值得敬佩的,比很多人厉害多了
雪    币: 55
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Jmdebugger 活跃值 2019-4-19 14:36
18
0
游客
登录 | 注册 方可回帖
返回