关于安全:Fairplay-DRM与混淆实现的研究

25次阅读

共计 9557 个字符,预计需要花费 24 分钟才能阅读完成。

钻研 Fairplay DRM(Digital Rights Management,即数字版权保护)最要害的两点是受权和加密。但长久以来,对于 App DRM 的钻研却很少,而就是在这样的前提下,Fairplay DRM 又为 iOS App 的平安钻研叠加了一层“妨碍”。咱们通过剖析混同零碎的设计和实现过程中的问题,克服调试跟踪的阻碍,设计了多种动态和动静的反抗计划;同时通过大量的逆向工程,填补了平安钻研人员对 macOS 零碎机制中,对于 Fairplay 这一部分的认知空白。

什么是 DRM?

DRM 全称 Digital Rights Management,即数字版权保护。苹果为了爱护 App Store 散发的音乐 / 视频 / 书籍 /App 免于盗版,开发了 Fairplay DRM 技术,并申请了很多相干的专利,比拟有代表性的如:

  • US8934624B2: Decoupling rights in a digital content unit from download
  • US8165286B2: Combination white box/black box cryptographic processes and apparatus
  • ES2373131T3: Safe distribution of content using descifrado keys

长久以来,对于 App DRM 的钻研很少,而 DRM 的要害是受权和加密。破解 Fairplay DRM 加密的形式俗称“砸壳”,这是进行 iOS App 平安钻研的必要前提。自从 2013 年苹果引入 App DRM 机制当前,诞生了如 Cluth、Bagbak、Flexdecrypt 这样的经典“砸壳工具”,而此类“砸壳工具”通常须要越狱设施的反对,因而具备肯定的局限性。

2020 年公布的 M1 Mac 将 Fairplay DRM 机制引入了 MacOS,因为 Mac 设施的权限没有 iOS 严格,因而咱们得以在 MacOS 上摸索更多 Fairplay DRM 的原理,最终目标是使解密流程不受 Apple 平台的限度。上面,咱们先来聊聊 Apple 中是如何实现的?

Apple 上 DRM 的实现:Fairplay DRM

LC_ENCRYPTION_INFO 中的标记

加密的 MachO 含有 LC_ENCRYPTION_INFO 字段,其中 cryptoff 标识了加密局部在文件中的起始偏移,cryptsize 标识了加密局部的尺寸,cryptid 则表明了加密的办法。Fairplay DRM 爱护下的 App,其加密尺寸为 4096 的倍数,加密形式标识为 1。

而负责解密 Mach- O 的组件次要包含:内核态的 FairplayIOKit 和用户态的 fairplayd

Fairplay 的 Open

MacOS 的 XNU Kernel 中有 text_crypter_create_hook 这个导出符号,IOTextEncryptionFamily 驱动则注册了这个 Hook,并作为桥梁,将调用转发给了 FairplayIOKit 内核驱动。

最终负责解决的函数是:

com_apple_driver_FairPlayIOKit::xhU6d1(
  char const* executable_path,
  long long cpu_type,
  long long cpu_subtype,
  rp6S0jzg** out_handle
)

尔后,内核中的 FairplayIOKit 开始初始化,通过 host_get_special_port 中的 unfreed port 发送 MIG 调用到用户态的 fairplayd,fairplayd 开始解决 SC_Info 目录下的 sinf 和 supp 文件,并将解决的数据返回给内核中的 FairplayIOKit。

注:用户态的 fairplayd 具体工作流程不在本文探讨范畴内。

其中 MIG 调用的构造如下:

struct FPRequest{
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_ool_descriptor_t ool;
    NDR_record_t ndr;
    uint32_t size;
    uint64_t cpu_type;
    uint64_t cpu_subtype;
};

struct FPResponse{
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_ool_descriptor_t ool1; //supf 文件映射
    mach_msg_ool_descriptor_t ool2; //unk,反比与加密内容的尺寸
    uint64_t unk1;
    uint8_t unk2[136];
    uint8_t unk3[84];
    uint32_t size1;
    uint32_t size2;
    uint64_t unk5;
};

实现所有调用后,返回的构造 rp6S0jzg* 理论是一个 uint32_t 类型的 handle,接下来则能够用这个 handle 来实现解密操作。

Fairplay 的 Decrypt Page

后面提到的 Fairplay Open 操作最终返回了一个 pager_crypt_info 的构造体,其中 page_decrypt 的 Hook 由 IOTextEncryptionFamily 驱动接管,并最终转发给 FairplayIOKit。

最初,FairplayIOKit 中负责解密的函数定义如下:

com_apple_driver_FairPlayIOKit::bvqhJ(
  rp6S0jzg *hanlde,
  unsigned long long offset,
  unsigned char const* src,
  unsigned char * dst
)

至此,Fairplay 的解密逻辑实现调用。值得注意的是,在 Fairplay DRM 中,page 的概念为 4096bytes。

那么,用户态 fairplayd 解决的 sinf 和 supp 文件又是什么样子的呢?

SINF 和 SUPF 文件

构造

用户态的 fairplayd 会读取随 IPA 携带的两个重要文件:SINF 和 SUPF,存储在 App 的 SC_Info 目录下。

其中 SUPF 文件和 IPA 一起散发,每个用户的 IPA 和 SUPF 文件都是统一的,其中 SUPF 文件中保留了加密 Mach- O 的密钥,然而密钥自身被另外的机制加密。而 SINF 文件则作为每个用户的 DRM 许可,记录了购买用户的标识符和姓名,以及解密 SUPF 须要的信息,因而在 Sandbox 策略下,App 无奈读取本身的 SINF 文件,以避免其被作为惟一 ID 追踪用户

SINF

SINF 文件是一个 LTV+KV 构造的文件,它的字段如下所示:

sinf.frma: game
sinf.schm: itun
sinf.schi.user: 0xdeadbeef
sinf.schi.key : 0x00000005
sinf.schi.iviv: 0x12345678901234567890123456789012
sinf.schi.righ.veID: 0x000007d3
sinf.schi.righ.plat: 0x00000000
sinf.schi.righ.aver: 0x01010100
sinf.schi.righ.tran: 0xdc64f80c
sinf.schi.righ.sing: 0x00000000
sinf.schi.righ.song: 0x59a73c58
sinf.schi.righ.tool: P550
sinf.schi.righ.medi: 0x00000080
sinf.schi.righ.mode: 0x00002000
sinf.schi.righ.hi32: 0x00000004
sinf.schi.name: User Name
sinf.schi.priv: (432 Bytes Private Key)
sinf.sign: (128 Bytes Private)

SUPF

SUPF 文件次要分为三个局部,咱们将其命名为 Key Segments、Fairplay Certificate、RSA Signature,其中 Key Segments 能够含有多个子 Segment,用来保留多个架构的解密信息。

KeyPair Segments:
    Segment 0x0: arm64, Keys: 0x36c/4k, sha1sum = e369546960d805dd1188d42e3350430c7e3a0025

Fairplay Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            33:33:af:08:07:08:af:00:01:af:00:00:10
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple FairPlay Certification Authority
        Validity
            Not Before: Jul  8 00:48:29 2008 GMT
            Not After : Jul  7 00:48:29 2013 GMT
        Subject: C=US, O=Apple Inc., OU=Apple FairPlay, CN=AP.3333AF080708AF0001AF000010
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (1024 bit)
                Modulus:
                    00:b0:01:16:4b:62:b2:37:8d:60:12:4f:02:15:15:
                    a0:32:1b:e8:ed:44:ed:e9:17:5b:ec:9e:5d:11:24:
                    5a:66:2f:dc:a3:25:aa:52:70:e1:09:22:09:4b:65:
                    0f:67:f5:82:dc:af:78:9b:4c:45:f3:b4:f4:77:aa:
                    fc:a3:b2:84:c3:8b:09:c6:2e:55:f5:14:85:07:ac:
                    ae:0d:ff:ff:ca:41:3b:44:cb:52:b6:28:60:55:23:
                    35:8d:26:71:c6:12:a5:e0:72:58:09:3c:4a:9e:b6:
                    63:df:2a:91:94:27:eb:65:0a:b2:36:45:11:c1:91:
                    43:58:12:d9:e5:18:a1:ad:db
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Data Encipherment, Key Agreement
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                7B:07:34:81:A5:75:D0:F6:11:BB:D2:36:3F:79:93:4B:A1:70:EB:CF
            X509v3 Authority Key Identifier: 
                keyid:FA:0D:D4:11:91:1B:E6:B2:4E:1E:06:49:94:11:DD:63:62:07:59:64

    Signature Algorithm: sha1WithRSAEncryption
         06:11:4e:87:ed:b1:08:70:c2:0d:e4:d2:94:bb:7f:ee:50:18:
         c0:2a:21:34:0e:99:1f:bf:60:a2:58:d0:0c:28:3d:03:5b:ab:
         4e:72:69:ba:41:52:45:b2:29:27:4a:c8:ba:7f:b5:9b:63:78:
         b1:68:41:40:59:3f:05:8a:57:74:c5:63:30:cc:f3:20:41:c0:
         3c:65:d4:0d:22:47:f3:97:76:e6:d6:3c:eb:e7:20:78:10:59:
         fd:96:09:82:c3:41:f0:5f:d0:3e:91:44:6d:77:3f:a5:d9:da:
         f0:f7:53:ad:94:61:28:1c:4c:40:3b:17:2b:dd:e3:00:df:77:
         71:22

RSA Signature: 6aeb00124d62f75f5761f7c26ec866a061f0776be7e84bfad4b6a1941dbddfdb3bd1afdcc5ef305877fa5bee41caa37b1a9d4ce763cf7d2cb89efa60660a49dd5ddff0f46eee7cd916d382f727d912e82b6e0a62e8110c195e298481aa8c8162faac066ef017c6c2c508700d7adb57e0c988af437621e698946da1b09adf89e9

上面,咱们来聊聊 Fairplay DRM 的混同原理和实现。

混同原理和一些实现

LLVM Pass

LLVM 是一个低劣的编译器框架,其中,咱们能够将其大略的分为前端、中端、后端:

配图节选自 CMU 的 CS 15-745 课程:https://www.cs.cmu.edu/~15745。

前端负责将高级语言转化为 LLVM IR;中端解决 LLVM IR,实现一系列的剖析、优化工作,咱们称之为 Pass,再次输入 LLVM IR;后端则负责将 LLVM IR 转化为机器码。其中,中端的玩法特地丰盛,根本的优化工作:如死代码打消、常量折叠都在这一部分实现;Address Sanitizer、PC Sanitizer 等编译器插桩也是在这里进行的;其余的混同框架如探讨的较多的 ollvm 以及 Hikari,甚至包含苹果的混同机制,也都是基于此实现。

这一混同形式能够根本的分为控制流混同和数据流混同,除此之外的一些混同形式,比方 VMP 等,不在本文探讨范畴内。

makeOpaque

在编译器中,为了避免一些具体的表达式被优化,咱们会将表达式进行等价变动,咱们临时将这样的操作定义为 makeOpaque(如 Safari 的 JavascriptCore,其 JIT 组件 B3 就提供这样的机制),C++ 伪代码如下:

Expression* makeOpaque(Expression *in);

不通明谓词(Opaque Predicate)

谓词(Predicate)在计算机中,指的是执行后为 True 或 False 的表达式。数论外面的一些论断能够作为咱们生成 不通明谓词 的根底,这些不通明谓词的后果恒为 True 或恒为 False。比方下列表达式中,y 执行的后果就恒为 True:

uint32_t x = 0;
bool y = ((x * x % 4) == 0 || (x * x % 4) == 1);

不通明谓词利用到混同中的一个例子就是 bogus CFG。
如源语句如下:

foo1();
foo2();

通过变换,咱们增加了一个虚伪的分支(即 bogus CFG)

foo1();
if (false)
  junk_code();
else
  foo2();

然而如果没有通过特地解决,编译器、反编译器的死代码打消就会将虚伪分支去除掉,因而咱们须要 makeOpaque 的引入,假如咱们引入了后面示例中的表达式:

foo1();
uint32_t x = rand();
bool y = ((x * x % 4) == 0 || (x * x % 4) == 1);
if (!y)
  junk_code();
else
  foo2();

那么如果编译器、反编译器没有相应的辨认机制的话,这一部分的死代码就保留了下来,通过在死代码外面插入大量烦扰指令,能够为逆向的人员带来极大的困扰。经测试在 -O2 优化下,Clang 11 曾经能够辨认这个规定,然而 GCC 5.4 无奈辨认。

可逆变换

这里咱们介绍一下目前混同技术中罕用的等价变换形式。

异或

异或规定是最常见的变换,这里不再赘述。

x ^ c ^ c = x;

仿射变换(Affine transformation)

咱们先来看一下仿射函数。

上面咱们来看一下理论利用。

因为计算机中的运算属于隐式的模运算,因而会具备一些有意思的性质。如对于一个 uint32 上的运算,模运算逆元定义如下:

// 对于
uint32_t a, r_a;

// 如果满足
(a * r_a) % UINT32_MAX == 1;

// 那么 a 和 r_a 互为模反元素

对于互为模反元素的 a 和 r_a(可通过扩大欧几里得算法求得),有这样的个性:

uint32_t x = rand();
uint32_t y1 = a * x + c;
// 那么满足
x == ra * y1 +  (- ra * c)

最初举个例子来阐明:

// 对于互为模反元素的 4872655123 * 3980501275,取
uint32_t x = 0xdeadbeef;
uint32_t c = 0xbeefbeef;
// 则 -ra * c = 0x57f38dcb,且
((x * 4872655123) + 0xbeefbeef) * 3980501275 + 0x57f38dcb == x
/*
可在 lldb 中验证如下
(lldb) p/x uint32_t x=0xdeadbeef; (uint32_t)(((x * 4872655123) + 0xbeefbeef) * 3980501275 + 0x57f38dcb)
(uint32_t) $8 = 0xdeadbeef
*/

MBA 表达式(Mixed Boolean-Arithmetic Expression)

MBA 表达式是把算术运算(+,-,*,/)和位运算(&,|,~)混合在一起用以暗藏本来表达式的混同办法。它基于不同的数学原理存在多种形式,这里次要介绍多项式 MBA,这是目前混同技术中最常遇到的模式。

相似的,在 Fairplay 混同中用到的 MBA 表达式为:

//OperationSet(+, -, *, &, |, ~)
x - c = (x ^ ~c) + ((2 * x) & ~(2 * c + 1)) + 1;

而应用 MBA 进行混同操作次要依附以下两个步骤:

不通明常量(Opaque Constant)

不通明常量是基于 MBA 混同的办法,用于暗藏数据流中的常量。它应用了置换多项式,是一种在无限域上的可逆多项式。

控制流平坦化

这一部分是逆向工程中探讨的最热门的话题,行将失常的控制流转换等价替换为一个状态机,从而烦扰动态的控制流剖析,业界也有较多的解决方案。同时因为 Fairplay DRM 中没有显著用到这种类型的混同,不再多探讨。

非间接跳转(Indirect Branch)

将一些基本块的起始地址保留在全局变量中,通过不通明常量的生成,使得反汇编工具和肉眼无奈间接获取到基本块跳转的指标,模型如下:

// 记录基本块地址到全局查找表 LUT
LUT[i] = PC;

// 执行跳转
jmp/call LUT[makeOpaque(i)]

具体的实例:

这样,逆向人员就无奈间接获取跳转的指标函数、基本块了。同理,通过将判断语句的条件映射到跳转表,也能够实现对条件跳转的混同。

所以,在逆向被混同的 Fairplay 代码时,IDA Pro 大多数时刻,只能辨认进去函数的第一个基本块,无奈剖析出函数的边界

跨函数混同 + 调用约定混同

失常状况下,编程语言如 C 语言的参数传递遵循特定的调用约定,然而局部混同工具会对一些外部函数的调用约定进行批改,以 Fairplay DRM 为例:

咱们能够看到惯例的以寄存器和栈传递参数的形式被替换成了以堆传递参数的形式了,在结构好了构造体的状况下,这个参数传递的特色能够被清晰的看进去。同时,这里面对一些传递的参数进行了异或混同,在子函数外面再复原进去,使得咱们难以间接失去原始数据,而动态剖析的工具比方 IDA Pro 也不反对跨函数的数据流剖析。

更重大的是,一些影响子函数运行的重要依赖数据,被晋升到了父函数内,导致 在没有复原调用关系前,咱们根本无法揣测子函数的运行流程。

那么,Fairplay DRM 的破解之道就是要找到它的弱点。

Fairplay 混同的弱点

通过前边的工作,咱们曾经能 Fairplay 失常的实现关上和解密工作了,通过一系列的动态剖析和追踪调试,咱们发现了这一套混同零碎的一些反抗计划。

这些问题的实质起因是:混同零碎在 IR 层面设计,对机器相干的局部操作没有混同,因而在生成的机器码外面,咱们能够推断失去混同前的一些特色信息

函数边界辨认

后面提到,因为 Fairplay 用到了非间接跳转的混同技术,IDA Pro 无奈间接剖析函数的边界。通过跟踪,咱们发现在 arm64e 设施下,该内核驱动中,同一个函数的所有基本块在运行到跳转指令时,均应用了同一个 PAC Context,或者称之为 PAC Modifier。

借由这个个性,咱们能够将函数的边界和基本块分组,只管目前为止这些基本块之间并不是连通的。

非间接跳转

对于 无条件跳转,咱们通过设置断点跟踪执行流,就能够解决了。

再通过 KeyPatch 这样的工具,咱们能够将一些简略的函数复原到比拟易于了解的境地。

然而这里的难点在于复原混同外面的 非间接跳转指令,如下图所示:

对于这个跳转指令,咱们能够生成如下的表达式:

//cmp x0, #0
w10 = qword[x12 + (EQ * 0xB + w19) << 3]
//0xB 代表两个基本块的在 LUT 中的下标差

通过 CSET 指令的模式,咱们曾经能够推断跳转指令应该是 J.NE 或者 J.EQ 了,通过咱们的调试器插件,咱们能够失去其中一个分支的跳转地址和本来的跳转指令,再通过表达式的信息,咱们能够很快推断出另外一个分支的地址。

再通过 Keypatch,咱们能够失去混同前的分支语句构造:

至此,咱们曾经能够残缺地复原 Fairplay 的大多数控制流了。

数据流混同

这一部分在后面曾经提及到了一些,目前咱们曾经找到了 MBA 表达式的模式,但还没能找到 Fairplay 中生成不通明常量的残缺规定。其中 MBA 表达式的重写规定目前看起来仅有一个,即:

x - c = (x ^ ~c) + ((2 * x) & ~(2 * c + 1)) + 1;

一些基于模式匹配的工具,比方 D810 曾经能够比拟好的解决这样的状况了。

结束语

目前,咱们曾经能够获取到解密每一段 Mach- O 的 AES 密钥了,通过大量的调试和反混同,咱们曾经得出了这些密钥生成的初步论断。咱们心愿最终的目标是不依赖 Apple 设施的前提下,实现 Fairplay DRM 加解密的钻研。

最初,附上源码,欢送大家进行参考和钻研。

参考文献

  • Eyrolles, N. (2017). Obfuscation with Mixed Boolean-Arithmetic Expressions: reconstruction, analysis and simplification tools (Doctoral dissertation, Université Paris-Saclay)
  • https://github.com/obfuscator-llvm/obfuscator
  • https://github.com/HikariObfuscator/Hikari
  • https://github.com/keystone-engine/keypatch
  • https://eshard.com/posts/d810_blog_post_1

作者简介

吴聊、落落、朱米,均来自美团信息安全部。

浏览美团技术团队更多技术文章合集

前端 | 算法 | 后端 | 数据 | 平安 | 运维 | iOS | Android | 测试

| 在公众号菜单栏对话框回复【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。

| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。

正文完
 0