首页
社区
课程
招聘
[原创] hook objc_msgSend方法以获取OC方法入参
2022-1-24 20:24 45762

[原创] hook objc_msgSend方法以获取OC方法入参

2022-1-24 20:24
45762

hook objc_msgSend

objc_msgSend是iOS里最重要的方法。

 

按道理来说,hook objc_msgSend就可以获取到app中所有调用的OC方法名和参数等。

 

所有的OC方法都需要经过objc_msgSend方法消息转发寻找方法IMP。

 

参考hook objc_msgSend从而获得OC方法执行时间

简说objc_msgSend方法

objc_msgSend源码参考 苹果openSource

 

此方法考虑效率,使用汇编编写,此方法大致上有4个关键步骤:

  1. 获取传入的target的class
  2. 找到class对应方法的缓存
  3. 尝试在缓存bukets中找到方法的具体实现。如果没有在cache中找到,则进入消息转发,深层查找,找到后,将方法和对应的IMP写入cache中。
  4. 直接调用方法的实现

综上看,这个方法是一个"跳板"函数,并且它是线程安全的。

 

上述的4个步骤中,1,2,4是线程安全的,3步骤利用一些trick保障了线程安全,具体超出了本文的范围,可查看剖析objc_msgSend,其中对objc_msgSend方法的汇编实现,逐行讲解。

想法1 视为普通C函数hook

因为根据 #include <objc/message.h>objc_msgSend函数的定义如下:

1
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

所以将它视为普通的C函数,利用 fishhook / cydia substrate hook,结合va_list分析传入的参数,应该就可以。

 

但是,不可以。通过据这里介绍,arm 64位上的va_list结构改变了,不能使用va_list了,因此此方法不能实现。

 

没有验证正确性。

想法2 利用fish Hook+汇编 hook

既然利用va_list获取参数不可行。

 

参考objc_msgSend源码,因为在arm 64上,x0 - x8寄存器用于传参,所以结合汇编代码,应该可以hook,crx0715已经成功,具体可以查看它的讲解。

 

使用fishhook很好,因为fishhook是导出表hook,不会修改原方法的实现。

 

大概实现如下:

 

 

其中,保存寄存器,即 使用stp命令,将寄存器保存到栈上。

 

提高栈帧SP,减小地址即可。因为iOS上内存栈帧由高地址向低地址,即后入的参数在内存低地址。

1
"stp x8, x9, [sp, #-16]!\n" \

偏移寻址的标记后多了一个 !,在执行完上文的 stp 指令后,还会使 sp 寄存器也产生偏移。

 

同理,还原寄存器,使用ldp命令降低栈帧SP。

想法3 利用inline hook + 汇编hook

既然fishhook可行,inline hook也可以。

 

区别在于inline hook会修改原始方法的前3条指令,备份函数会将这前3条指令备份下来,然后br跳转到原始方法的第四条指令。如此调用备份函数就好像调用原始objc_msgSend一样。具体inline hook的如何修改的原理就不赘述了。

 

具体流程和fishhook一致,不过经过实验有个坑,就是在before_objc_msgSend_inlineafter_objc_msgSend_inline中暂时不能调用OC方法,因为会造成如下死循环。

 

得到objc_msgSend的参数

由于参数保存在x0-x8寄存器中,那么在before_objc_msgSend中对寄存器分析,就可以获取到入参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 打印调用的OC方法的对象和方法名,和最多两个参数。
void printSpecificParam_fish(id self, SEL _cmd, uintptr_t param1, uintptr_t param2,uintptr_t lr)
{
    // 此方法中暂只能使用C方法,使用OC方法可能会导致寄存器异常导致崩溃,经测试,发生在相同方法调用相同方法时崩溃
    // NSlog可以用
    const char * className = object_getClassName(self);
    const char * selector = sel_getName(_cmd);
    //NSLog(@"class : %s, methodname : %s",className,selector);
    if ( strcmp( selector, "fileExistsAtPath:" ) == 0) {
        NSLog(@"class : %s, methodname : %s, param1 : %@",className,selector,param1);
    }else {
        NSLog(@"class : %s, methodname : %s",className,selector);
    }
}

这里有个坑,self参数,在一些app中会引入引用计数问题导致崩溃,还没有具体查询。作为TODO吧,现在

 

的printSpecificParam_fish参数入参需要修改一下,将self去除:

1
2
3
4
5
void printSpecificParam_fish( SEL _cmd, uintptr_t param1, uintptr_t param2,uintptr_t lr)
{
        const char * selector = sel_getName(_cmd);
    NSLog(@"methodname : %s",selector);
}

运行结果

 

可以看到打印了所有的OC方法的方法名和参数。

最后

完成代码可以参考github地址


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2022-1-25 11:13 被LeoW丨编辑 ,原因: 更换图片
收藏
点赞2
打赏
分享
最新回复 (7)
雪    币: 5
活跃值: (116)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
peter_puff 2022-1-24 22:09
2
0
太强了 TQL
雪    币: 3710
活跃值: (3924)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 2022-1-25 08:55
3
0
666666666
雪    币: 5485
活跃值: (3247)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
尐进 2022-1-25 17:27
4
0
mark 支持一下
雪    币: 249
活跃值: (464)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chenxia0 2022-2-18 18:51
5
0
实测了一把,有两个小问题:1.如何打印方法对应的类名,2.这种打印方式,消耗内存,启动速度明显变慢。不过大佬的思路不错,在fishhook中有对寄存器的操作,优秀。
雪    币: 436
活跃值: (461)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
LeoW丨 2022-4-7 16:38
6
0
chenxia0 实测了一把,有两个小问题:1.如何打印方法对应的类名,2.这种打印方式,消耗内存,启动速度明显变慢。不过大佬的思路不错,在fishhook中有对寄存器的操作,优秀。
1. 打印类名后来注释了,有偶现的崩溃,还不稳定。可以修改一下代码,x0即为self,把x0传入printSpecificParam_fish方法,利用object_getClassName方法就能获取到类名。
2. hook会导致启动速度变慢,还需要优化,回头搞一下
雪    币: 121
活跃值: (1517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xxRea 2022-4-14 20:36
7
0
mark 支持一下
雪    币: 0
活跃值: (196)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
breakM 2022-4-20 11:48
8
0
mark 支持一下
游客
登录 | 注册 方可回帖
返回