探索 facebook iOS 客户端 - section FBInjectable


现象

MachOView查看Facebook的可执行文件,发现 FBInjectable 和 fbsessiongks 的数据段,这篇文章就探索下 FBInjectable 数据段的产生与用途。

如何定位

设备:iPhone5 越狱 iOS8.4 armv7

砸壳

Clutch 或 dumpdecrypted,获取到未加密的Facebook armv7 可执行文件。

初步查找 strings

使用strings 搜搜关键词FBInjectable,可知可以通过字符串作为切入点。

使用Hopper和IDA分析

使用Hopper和IDA分析好。两个App各有优缺点,配合使用。

分析较慢,我的MBP CPU2.2 i7 分析1个小时以上。

分析完成后就可以畅游arm了。

Hopper中初步定位

搜索字符串FBInjectable

查看存在交叉引用的一个

跳转过去查看,可知地址在 0x0334cc1c,且FBInjectable是作为 getsectiondata 的第三个参数。

getsectiondata 的调用地址为 0x0334cc20。

getsectiondata 的定义如下:

反汇编:

需要重点关注下,r11这个变量。Hopper反汇编的代码貌似丢掉一些很关键的r2的信息。但看完能大概知道这里在遍历 getsectiondata的返回值,每4个字节做了一些处理。

如果IDA 有F5 反汇编功能,可以看到下图。这里没有丢掉关键的信息。v19作为返回值,转换为DWORD指针(做Windows开发比较熟悉,double word,word 是双字节,DWORD就是四字节),然后又把这个指针解引用。

也就是把getsectiondata的返回值buffer中的前四个字节当做字符串的内存地址。

MachOView 确认FBInjectable含义

再次看 FBInjectable 段的前四个字节,B8DB8404,由于little-endian的原因,内存地址为0x0484DBB8。

Hopper中跳转到这个地址:

其他四个字节都是这样。

lldb 确认getsectiondata返回值含义

为确认Facebook启动后是否调用了 getsectiondata,并传入了FBInjectable,可以先条件断点。

使用debugserver启动App:

everettjfs-iPhone:~ root# debugserver -x backboard *:1234 /var/mobile/Containers/Bundle/Application/A7811200-13B6-4053-BAED-8D3E8DE7C929/Facebook.app/Facebook

添加条件断点:

70 = F
95 = _

(lldb) br set -n getsectiondata -c '(int)*(char*)$r2 == 70'

继续运行,发现可以断下来。

单步运行,打印返回值 r0的数据。

到这里我们发现个问题,与FBInjectable section的数据不一样。

但发现个规律,每4位相减正好是 ASLR 偏移地址。(例如: 0x0488bbb8 - 0x0484dbb8 = 0x3e000)

也就是说 FBInjectable 数据段 的数据在调用getsectiondata之前就被修改了。这里暂且忽略修改的方法(下文会介绍一种修改方法),继续探索。

打印当前方法的 返回值。

可见这里与开始strings查找FBInjectable时的结果很相似,

PS:这一步使用条件断点会导致启动特别慢。但条件断点的目的只是为了确认是否有这个调用。由于调试中要多次启动App,也可以直接断点到目标地址。(启动时,先断点到start,然后image list 查看ASLR偏移地址,再计算出getsectiondata的地址,然后 br s -a ADDRESS)

初步结论

由此可知,FBInjectable中存储的是

这类字符串的地址,armv7(32位)下每个地址占用4个字节,上图18个地址总共占用72个字节。通过FBInjectable的数据可以获取到这18个字符串。

class-dump查看

在头文件中搜索fb_injectable,

再看lldb调用栈

图中看到好多folly的符号,folly是Facebook开源的专注于性能的C++ Library,但不知为何会对lldb的符号有这么大的影响。(有时间需要进一步了解下lldb的符号如何查找的)。不过,地址都是正确的。可以通过frame中的地址减去ASLR偏移地址,得到文件中的地址。

根据调用堆栈,可以跟踪到如下位置:

这里跨度可能有点大,但根据调用栈中的那些frame的地址,很容易看到下图内容(这只是18个配置中的一个)。

大概流程如下:

FBNavigationBarSearchFieldLayout 类的

+ (float)_calculateLeftOffsetForController:(id)arg1 isRoot:(BOOL)arg2 hasLeftMessengerButton:(BOOL)arg3;

调用了 FBIntegrationManager 类的

+ (Class)classForProtocol:(id)arg1;

进一步调用了

+ (id)classesForProtocol_internal:(id)arg1;

classesForProtocol_internal 会在首次调用时(dispatch_once)时加载FBInjectable的内容并获取这18个字符串,进而转化为对应的类。

classForProtocol的参数是 协议 FBNavigationBarConfiguration,通过这个协议获取到 类FBNavigationBarDefaultConfiguration。

最终调用静态方法,

再看头文件

这18个类有几个共同点

  1. 都包含方法 fb_injectable。
  2. 都包含方法 integrationPriority。
  3. 都是静态方法。
  4. 都会实现一个类似名称的协议。例如:FBNavigationBarDefaultConfiguration 实现协议 FBNavigationBarConfiguration。

  5. 第4条提到的协议都会继承另一个协议 FBIntegrationToOne。

结论

至此,基本能知道FBInjectable的作用了,可以得出如下结论。

FBInjectable section用于在打包阶段更改一些配置。这个配置可能影响到界面(UI)、功能(Policy)等各个方面。

FBNewAccountNUXPYMKVCFactory,
FBPersonContextualTimelineHeaderDataSourceDefaultConfiguration,
FBTimelineActionBarDefaultConfiguration,
FBTimelineActionBarNuxPresentersDefaultConfiguration,
FBTimelineNavTilesFriendsFollowersDefaultConfiguration,
FBGroupsModuleDefaultConfiguration,
FBGroupsLandingWildeCoordinator,
FBEventsModuleDefaultConfiguration,
FBEventMessageGuestsDefaultCapability,
FBPhotoModuleDefaultConfiguration,
FBGrowthModuleDefaultConfiguration,
FBEventComposerKitDefaultConfiguration,
FBComposerDestinationOptionsDefaultPolicy,
FBBookmarksDownloaderConfiguration,
FBGroupCreationViewControllerDefaultStepsFactory,
FBNavigationBarDefaultConfiguration,
FBPersonalAppSuiteDefinitionProvider,
WildeKeys

这些类有以下共同信息:

  1. +(void)fb_injectable 方法。
    • 这个方法只是个标记。
    • 用于方便的查找到这个类。
  2. 都会实现一个与当前类对应的协议。
    • 例如:FBNewAccountNUXPYMKVCFactory类 对应 FBNewAccountNUXPYMKVCFactory协议。
    • 再例如:FBNavigationBarDefaultConfiguration类 对应 FBNavigationBarConfiguration协议。
    • 再例如:FBEventsModuleDefaultConfiguration类 对应 FBEventsModuleConfiguration协议。
  3. 这个对应的协议一定会实现 FBIntegrationToOne。也就是说当前类还包括 + (unsigned int)integrationPriority; 方法。
    • 例如:FBNewAccountNUXPYMKVCFactory、FBNavigationBarConfiguration、FBEventsModuleConfiguration都会继承 FBIntegrationToOne 协议。就导致 FBNewAccountNUXPYMKVCFactory等类也都包含 + (unsigned int)integrationPriority; 方法。
    • 这个方法用于指定查找优先级。
    • FBIntegrationToOne 从名称看,只能集成1个。就是根据优先级选择1个的意思。

例如:

float __cdecl +[FBNavigationBarSearchFieldLayout _calculateLeftOffsetForController:isRoot:hasLeftMessengerButton:]

FBNavigationBarConfiguration

循环判断哪个类实现了这个协议,最后找到FBNavigationBarDefaultConfiguration,然后获取shouldShowBackButton。

FBInjectable的创建

可以使用 __attribute((used,section("segmentname,sectionname"))) 关键字把某个变量的放入特殊的section中。

attribute 参考 http://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Variable-Attributes.html

例如:

char * kString1 __attribute((used,section("__DATA,FBInjectable"))) = "string 1";
char * kString2 __attribute((used,section("__DATA,FBInjectable"))) = "string 2";
char * kString3 __attribute((used,section("__DATA,FBInjectable"))) = "string 3";

PS:文章修改:上面代码unused改为used。不需要引用,即可避免release下被优化掉。

概括

编译时的配置选择机制。

编译时期选择某些配置类加入编译(精确说是,链接时期选择某些配置类的目标文件加入链接)或者配置优先级,App运行时通过FBInjectable获取到所有配置类,并使用每个配置类对应的协议获取当前使用的具体配置。

例子及进一步说明

Demo中模仿了这个机制。

代码: https://github.com/everettjf/Yolo/tree/master/FBInjectableTest

Demo中实现了三种配置类,每个配置类使用类似下面的代码自动创建FBInjectable 段。 (printf只是为了防止被编译器优化掉,应该有其他方法防止优化掉,暂时没找到,如果你知道,请告诉我哈 ,感谢iOS逆向工程群内的vr2d同学,attribute的第一个参数改为used,就可以避免release下被优化掉。还需认真呀,当时看到unused就感觉有点怪,但没有仔细查找含义。)

#define FBInjectableDATA __attribute((used, section("__DATA,FBInjectable")))
char * kNoteDisplayDefaultConfiguration FBInjectableDATA = "+[NoteDisplayDefaultConfiguration(FBInjectable) fb_injectable]";

@implementation NoteDisplayDefaultConfiguration

+ (void)fb_injectable{
}
+ (NSUInteger)integrationPriority{
    return 0;
}

+ (BOOL)showDeleteButton{
    return YES;
}
+ (UIColor *)noteBackgroundColor{
    return [UIColor blackColor];
}

@end

读取FBInjectable段:

        Dl_info info;
        dladdr(readConfigurationClasses, &info);
        
#ifndef __LP64__
//        const struct mach_header *mhp = _dyld_get_image_header(0); // both works as below line
        const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
        unsigned long size = 0;
        uint32_t *memory = (uint32_t*)getsectiondata(mhp, "__DATA", InjectableSectionName, & size);
#else /* defined(__LP64__) */
        const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
        unsigned long size = 0;
        uint64_t *memory = (uint64_t*)getsectiondata(mhp, "__DATA", InjectableSectionName, & size);
#endif /* defined(__LP64__) */

最后使用方式如下:

    Class config = [FBIntegrationManager classForProtocol:@protocol(NoteDisplayConfiguration)];
    NSLog(@"cls = %@",config);
    NSLog(@"color = %@",[config noteBackgroundColor]);

这样有什么好处

配置文件可分散在各自文件中,省去了统一注册配置文件的代码。这样配置文件更容易添加和删除。

探索中遇到的困难

http://iosre.com/t/facebook-app-fbinjectable-section/4685

当时还没有些Demo,以为需要手动修改,但写Demo的过程中才恍然大悟。

总结

以上步骤只是我在探索后重新整理的步骤,真正探索过程中可能步骤相互穿插。

奇怪的是Facebook 貌似没有在任何文章中提到这个“配置选择”的小方法。Twitter和Google都没有搜到任何关于FBInjectable的信息。还好可以通过逆向来探索。