推广

神奇的fishhook

iseeyu2年前 (2024-02-22)推广126

machOView查看地址

fishHook其实就是修改懒加载表(Lazy Symbol Pointers)、非懒加载表(Non-Lazy Symbol Pointers)中的符号地址的指向,从而达到hook的目的。
主要流程如下:

lazy symbol Point Table -> indirect Symbols -> symbol Table -> String Table

MachO 文件有个规律,__la_symbol_ptr 节中的第 i 个数据,在 Indirect Symbols 中有对应的体现,且 index 变成了 reserved1 + I。

printf() 对应的数据,在 __la_symbol_ptr 节中是第 10 个元素,__la_symbol_ptr 中的 reserved1 为 12,那么我们找到 Indirect Symbols 中的第 22 个元素:

我们拿着 0x50,去 Symbol Table 中找到 Symbol Table 中 index = 0x50 的数据

Symbol Table 中偏移量为 0x233 的字符串,就是 _printf

原理

这里要注意的是,fishhook只能hook到系统C函数。因为自定义函数的实现会在mach-o的__TEXT(代码段)里。编译后,调用自定义函数的指针,是直接指向代码段中的地址的。

参考:
fishhook的实现原理浅析

fishhook使用方法

api
struct rebinding {
///函数名称
  const char *name;
///替换函数地址
  void *replacement;
///保存原始函数地址变量的指针
  void **replaced;
};

int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

int rcd_rebind_symbols_image(void *header,
                             intptr_t slide,
                             struct rcd_rebinding rebindings[],
                             size_t rebindings_nel);
使用方法:
struct rebinding nsLog;
nsLog.name = "NSLog";
nsLog.replacement = nNSLog;
nsLog.replaced = (void *)&oNSLog;
struct rebinding rebinds[1] = {nsLog};

///传入结构体数组,数组大小
rebind_symbols(rebinds, 1);


static void (* oNSLog)(NSString *format, ...);
void nNSLog(NSString *format, ...) {
    oNSLog(@"%@",[format stringByAppendingString:@"被HOOK了"]);
}

fishhook 使用场景

1. FBRetainCycleDetector检查内存泄漏
+ (void)hook
{
#if _INTERNAL_RCD_ENABLED
  std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);
  rcd_rebind_symbols((struct rcd_rebinding[2]){
    {
      "objc_setAssociatedObject",
      (void *)FB::AssociationManager::fb_objc_setAssociatedObject,
      (void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject
    },
    {
      "objc_removeAssociatedObjects",
      (void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,
      (void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects
    }}, 2);
  FB::AssociationManager::hookTaken = true;
#endif //_INTERNAL_RCD_ENABLED
}

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)

static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
    {
      std::lock_guard<std::mutex> l(*_associationMutex);
      // 强持有
      if (policy == OBJC_ASSOCIATION_RETAIN ||
          policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
        _threadUnsafeSetStrongAssociation(object, key, value);
      } else {
        // We can change the policy, we need to clear out the key
        _threadUnsafeResetAssociationAtKey(object, key);
      }
    }
///执行替换的方法
    fb_orig_objc_setAssociatedObject(object, key, value, policy);
  }

维护内部map

void _threadUnsafeSetStrongAssociation(id object, void *key, id value) {
    if (value) {
      auto i = _associationMap->find(object);
      ObjectAssociationSet *refs;
      if (i != _associationMap->end()) {
        refs = i->second;
      } else {
        refs = new ObjectAssociationSet;
        (*_associationMap)[object] = refs;
      }
///
      refs->insert(key);
    } else {
      _threadUnsafeResetAssociationAtKey(object, key);
    }
  }
2. hook objc_msgSend,截取oc方法实现:

objc_msgSend一方面是参数不定的,而且是汇编实现。所以不能像NSLog一样简单hook,这里采用的主要思路是:

先用fish hook hook主 objc_msgSend

void smCallTraceStart() {
    _call_record_enabled = true;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        pthread_key_create(&_thread_key, &release_thread_call_stack);
        rebind_symbols((struct rebinding[6]){
            {"objc_msgSend", (void *)hook_Objc_msgSend, (void **)&orig_objc_msgSend},
        }, 1);
    });
}

然后在自定义hook_Objc_msgSend 里执行相应的汇编代码:

static void hook_Objc_msgSend() {
    save()
    __asm volatile ("mov x2, lr\n");
    __asm volatile ("mov x3, x4\n");
    call(blr, &before_objc_msgSend)
    load()
    call(blr, orig_objc_msgSend)
    save()
    call(blr, &after_objc_msgSend)
    // restore lr
    __asm volatile ("mov lr, x0\n");
    load()
    ret()  
}

上述指令的主要操作是:

保存寄存器
调用自定义的before_objc_msgSend
恢复寄存器
调用原始objc_msgSend
再次保存寄存器
调用after_objc_msgSend
恢复寄存器
返回

static inline void push_call_record(id _self, Class _cls, SEL _cmd, uintptr_t lr) {
    thread_call_stack *cs = get_thread_call_stack();
    if (cs) {
        //增加深度
        int nextIndex = (++cs->index);
        if (nextIndex >= cs->allocated_length) {
            cs->allocated_length += 64;
            ///扩容
            cs->stack = (thread_call_record *)realloc(cs->stack, cs->allocated_length * sizeof(thread_call_record));
        }
        thread_call_record *newRecord = &cs->stack[nextIndex];
        newRecord->self = _self;
        newRecord->cls = _cls;
        newRecord->cmd = _cmd;
        newRecord->lr = lr;
        if (cs->is_main_thread && _call_record_enabled) {
            struct timeval now;
            gettimeofday(&now, NULL);
            newRecord->time = (now.tv_sec % 100) * 1000000 + now.tv_usec;
        }
        
        printf("push_call_record操作 class:%s method:%s \n\n", object_getClassName(_cls),  sel_getName(_cmd));
    }
    
}


static inline uintptr_t pop_call_record() {
    thread_call_stack *cs = get_thread_call_stack();
    int curIndex = cs->index;
    int nextIndex = cs->index--;
    thread_call_record *pRecord = &cs->stack[nextIndex];
    
    if (cs->is_main_thread && _call_record_enabled) {
        struct timeval now;
        gettimeofday(&now, NULL);
        uint64_t time = (now.tv_sec % 100) * 1000000 + now.tv_usec;
        if (time < pRecord->time) {
            time += 100 * 1000000;
        }
        uint64_t cost = time - pRecord->time;
        if (cost > _min_time_cost && cs->index < _max_call_depth) {
            if (!_smCallRecords) {
                _smRecordAlloc = 1024;
                _smCallRecords = malloc(sizeof(smCallRecord) * _smRecordAlloc);
            }
            _smRecordNum++;
            if (_smRecordNum >= _smRecordAlloc) {
                _smRecordAlloc += 1024;
                _smCallRecords = realloc(_smCallRecords, sizeof(smCallRecord) * _smRecordAlloc);
            }
            smCallRecord *log = &_smCallRecords[_smRecordNum - 1];
            log->cls = pRecord->cls;
            log->depth = curIndex;
            log->sel = pRecord->cmd;
            log->time = cost;
            
        }
       
       
    }
    
    printf("截住后: \n");
    return pRecord->lr;
}

在执行objc_msgSend前后分别执行了before_objc_msgSend和after_objc_msgSend方法

在这里因为是在主线程上,可以自定义一个模型数组在before和after里获取到同一个index并且进行操作制定index下的模型,从而计算方法用时

缺点:

  1. 只能hook主线程方法
  2. 只能hook oc 方法

demo

参考:

  1. 戴铭 -对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时
  2. 字节工程师对上者的完善封装
  3. 国外逆向原创

可以深耕的点

  1. MatchO,Dyld加载相关知识(fishhook源码实现原理)
  2. arm 64 汇编学习(objc_msgSend实现代码)

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/55878.html

相关文章

第一代抖音网红下神坛

产业作者|黄尘 编辑 | 谭松 在短视频崛起之后,对于抖音来说,造星似乎是一夜之间的事。从2017年到2018年,办公室小野因为一些趣味视频,从一个名不见经传的普通人,一下子收获千万粉丝,甚至蹿红到国外的YouTube。 还有温婉,前抖音一姐,曾凭...

2022年最全的在家赚钱的副业,看到就是赚到

2022年最全的在家赚钱的副业,看到就是赚到

今天,我们不说别的,整理7个适合个人在家单干的副业。需要电脑,如果你没电脑就不用看了,最后两个,我们也在做,你可以看到最后了解。这些副业,大家多去实践,前期,每月三五千好赚,后期没有上限,只看你如何去运营操作。总之万事开头难,不要还没开始就想着一天几百几千的,没有那种好事。...

竞价推广账户搭建需要注意哪些问题。

竞价推广账户搭建需要注意哪些问题。

当企业想要做好营销的话,是少不了竞价推广的,而在所有的网络推广当中,就是有SEM的速度是最快的,也是能够直接看见成效的;但是我们要如何去做也逐渐的成为了一个难题,而在对SEM账户搭建的时候,也需要注意一些细小的问题,那么我们要如何去规避这些问题呢? 一、竞价关键词筛选 针对关键词,首先要...

节日营销案例复盘|三八妇女节,做女生,很OK!

节日营销案例复盘|三八妇女节,做女生,很OK!

在刚过去的妇女节中,各大的手段,可谓是花样十足!其中联想的【她OK】,给我的印象很是深刻。今天就和大家简单分享一下,我在这个案例中所学习到的~1.明确营销目标,平衡品牌权益营销目标不同,对应的营销方式也不同。有的营销以卖产品为主要目的,而在三八妇女节这种节日营销上,联想选择...

我来分享SEO优化常见的误区有哪些。

我来分享SEO优化常见的误区有哪些。

现在,seo这个行业跟百度引擎一样水涨船高,各类各样的seo公司如雨后春笋般的接踵冒出,每一家公司的手艺实力 却是参差不齐。对于一个SEO新人而言,必定会在进修与实践的整个过程中犯下良多错误,不经意间就会闯入一个又一个的。下面小编就来为大家总结一些seo优化的误区有哪些。 误区一...

上海品牌营销策划公司~奇正沐古

上海品牌营销策划公司~奇正沐古

上海公司~奇正沐古...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片