8488c1a519374646b9923a8bdb3eee43
安全攻与防篇:重签名

1.低级签名

在开始本篇之前,建议大家先看一下 密码学和加解密

安全校验

我们先思考一个问题:苹果的 App 是怎么做安全校验的?

密码学和加解密 讲解数字签名的小节,我们使用了客户端公钥加密、服务端私钥解密的方式。根据这个思路,我们对苹果的安全校验做一个猜测:由于苹果的软件、硬件都是自家提供,所以应用上传到【Store】之后,可以在【Store】的服务器使用私钥加密。【Apple】在自己的移动设备上内置一个公钥,移动设备从【Store】下载应用,使用设备上的公钥进行解密。

低级签名过程

上面这个思路,是苹果的真实做法吗?

答案很显然不是的,因为上面的做法存在一个弊端:就是所有的【App】都要经过【Store】进行下载,才能使用。但实际上,在移动设备上安装应用,是有多种方式的:

  • Store 下载安装;
  • 企业账号直接分发;
  • 开发者调试直接安装。

如果是必须要经过【Store】服务器的话,那么后面两种,我们就没法做了。怎么办呢?我们对刚才的方案,做进一步的思考。

在前面的方案沟通中,我们知道有 3 个角色:【Mac 电脑】、【移动设备】、【苹果服务器】。整个过程会涉及到两对【公钥/私钥】文件,我们按步骤来进行解说。

  • 【步骤一】:在 Apple 服务器上有一个私钥,记为 私钥A;而对应的公钥,记为 公钥A,在移动设备上。这是 Apple 控制的。

  • 【步骤二】:在开发时,一般我们会通过 【钥匙串访问】 的【证书助理】,生成一个【证书申请文件】。生成的 CSR 文件默认名称为【CertificateSigningRequest.certSigningRequest】,它是一个包含 Mac 电脑公钥信息的文件,这里记为 公钥B

    实际上,这个文件是一个 base64 编码的文件,可以通过指令来进行查看:

~/Desktop  cat CertificateSigningRequest.certSigningRequest
-----BEGIN CERTIFICATE REQUEST-----
MIICiDCCAXACAQAwQzEgMB4GCSqGSIb3DQEJARYRbGlnYW9mZW5nQDE2My5jb20x
EjAQBgNVBAMMCeadjumrmOWzsDELMAkGA1UEBhMCQ04wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDchNrrVcwLq2R5GTYk0B7BiilJxPIDVWXMJ7CukSTt
GEaGaYQXujqAYGL2VbaCvBSQpEgf4gZTM0JowhQmD07iGuMgX7fnfajavDwjfcJR
/kKkMUoYxjM9XyBCVX26JvloGlILFzCcMFl6p36C++B7iCX1uF8J5eHuOaYSkp2/
jHoseKIpgePvlYaHCsa1d/eAta1cXmnX7noIi5PliR4Y8CfMogsAXdp/8TCXYYCr
4m9APEDpBOc2IQwht/jjWEt97x5XCpGxENEiXLgLEg5KzYzUO3aIlWX4JlTPwHQL
hgNeg0I2b5gW6JUMclN6tSP0gtRQeCj3M4jnwlAQKg0pAgMBAAGgADANBgkqhkiG
9w0BAQsFAAOCAQEA3G2vNQrbAUs/0TZYig1tHkR+dc1F+SIQiGy3q/jbRnOg9rX9
Wec3rsP6QkgfE2Q9LzWMj4tcwH2KHkg8U6y648nDCxDQaDXRfvT1HlrklejjTMfM
InkEQv8OlrbN5rzLVTyzDzTMCzrR4XP9y36MAcMo8JDhUrgnGz68hhqrLU5Rmglm
ooVpgAsnXc0VQXpY+pLkbuLj7AKOl/K2G56Z2QSH1hkaYAcDsYN+xISi3ITqndm2
PBp4s9HzoZD1hq6n3ngIDr97ywmLLcDs0CWiJvAlC7MJMlyF5QYFRLh5u65nFPFG
+SxZg4jrvRqkxuSTfdZEKd0G9ZKb6YDICG/6cA==
-----END CERTIFICATE REQUEST-----

我们也可以通过下面的命令来查看:

~/Desktop  openssl asn1parse -i -in CertificateSigningRequest.certSigningRequest
    0:d=0  hl=4 l= 648 cons: SEQUENCE
    4:d=1  hl=4 l= 368 cons:  SEQUENCE
    8:d=2  hl=2 l=   1 prim:   INTEGER           :00
   11:d=2  hl=2 l=  67 cons:   SEQUENCE
   13:d=3  hl=2 l=  32 cons:    SET
   15:d=4  hl=2 l=  30 cons:     SEQUENCE
   17:d=5  hl=2 l=   9 prim:      OBJECT            :emailAddress
   28:d=5  hl=2 l=  17 prim:      IA5STRING         :ligaofeng@163.com
   47:d=3  hl=2 l=  18 cons:    SET
   49:d=4  hl=2 l=  16 cons:     SEQUENCE
   51:d=5  hl=2 l=   3 prim:      OBJECT            :commonName
   56:d=5  hl=2 l=   9 prim:      UTF8STRING        :Gof
   67:d=3  hl=2 l=  11 cons:    SET
   69:d=4  hl=2 l=   9 cons:     SEQUENCE
   71:d=5  hl=2 l=   3 prim:      OBJECT            :countryName
   76:d=5  hl=2 l=   2 prim:      PRINTABLESTRING   :CN
   80:d=2  hl=4 l= 290 cons:   SEQUENCE
   84:d=3  hl=2 l=  13 cons:    SEQUENCE
   86:d=4  hl=2 l=   9 prim:     OBJECT            :rsaEncryption      ;说明用的 rsa 算法
   97:d=4  hl=2 l=   0 prim:     NULL
   99:d=3  hl=4 l= 271 prim:    BIT STRING
  374:d=2  hl=2 l=   0 cons:   cont [ 0 ]
  376:d=1  hl=2 l=  13 cons:  SEQUENCE
  378:d=2  hl=2 l=   9 prim:   OBJECT            :sha256WithRSAEncryption     ;对公钥使用 sha256 哈希加密
  389:d=2  hl=2 l=   0 prim:   NULL
  391:d=1  hl=4 l= 257 prim:  BIT STRING

可以看到,CSR 文件除了有公钥之外,还有其他的很多信息。

  • 【步骤三】:通过 CSR 文件,向苹果服务器申请证书。苹果服务器使用 私钥A,对 公钥 B 和 公钥 B 的哈希值,进行加密,生成 证书。我们将证书下载,放在 Mac 电脑上。我们可以在钥匙串中看到下载的证书。

    其中,上图中的【专用秘钥】,就是对应 CSR 文件中公钥的私钥,这里记为 私钥B

  • 【步骤四】:使用 私钥B,对 App 进行 签名,同时将 证书 放入可执行文件,这样App 包含三部分内容:我们的 原始可执行文件、App 的签名、苹果服务器的证书

  • 【步骤五】:App 安装到移动设备,移动设备使用 公钥A,对 证书 进行解密,得到 公钥B 和 公钥B 的哈希值,比对公钥 B 和公钥 B 的哈希值,进行证书的校验。同时使用 公钥B,对签名进行解密校验。

整个过程大概如此,可以看到,苹果并没有对 App 本身进行校验,只是对 签名和证书 进行校验。这种方式能够保证,每次安装在手机上的 App,是经过苹果允许的。但是这个方式存在弊端:按照上面的操作,那么只需要申请一个证书,就可以安装在所有的设备上。为了避免安装滥用的问题,苹果加了两个限制:

  • 苹果服务器注册过的设备才可以安装;
  • 签名只能针对某一个 App。

2.代码签名原理

为了处理上面的问题,苹果添加了一个描述文件:Provisioning Profiles。这个描述文件包含这些信息:

  • 设备 ID 列表;
  • AppID;
  • Entitlements:权限文件,比如说可调试(get-task-allow)等;
  • ....

这里我们创建一个工程,直接真机编译或运行,然后打开生成的「App」文件,可以看到里面有一个描述文件:【embedded.mobileprovision】。我们通过如下指令来查看描述文件:

security cms -D -i embedded.mobileprovision

从结果可以看到,描述文件实际上是一个【xml】文件,我们在【Xcode】中新建一个【plist】文件,使用 【Open As Source Code】方式打开,将【xml】内容拷贝到文档,然后使用【Open As Property List】方式查看描述文件中的内容:

签名过程中,除了前面5个步骤介绍的,在【步骤四】,还会将描述文件拷贝到 App 安装包中。描述文件直接解压 app 文件看到描述文件【embedded.mobileprovision】:

那么证书和签名信息在哪呢?除了上面截图中的 _CodeSinature 中的文件,在可执行文件中也有:

【说明】:

  • Store 里的 App 中是不会有 描述文件的,校验是通过苹果服务器进行的。
  • 证书 和 描述文件 的生成,Xcode 是提供支持的。

3.手动重签

3.1 越狱应用 & Store 应用

通过 PP 助手(现在用不了,查看 神器下架 ,后续大家可以用越狱设备先从商店下载应用,然后手动砸壳),我们下载一个越狱版的微信和 Store 版微信。首先,我们通过下面指令来查看越狱版微信的签名信息:

~/Desktop  codesign -vv -d WeChat.app
Executable=/Users/GofLee/Desktop/WeChat.app/WeChat
Identifier=com.tencent.xin
Format=app bundle with Mach-O universal (armv7 arm64)
CodeDirectory v=20200 size=503759 flags=0x0(none) hashes=15735+5 location=embedded
Signature size=4297
Authority=(unavailable)
Info.plist=not bound
TeamIdentifier=88L2Q4487U
Sealed Resources version=2 rules=19 files=822
Internal requirements count=1 size=96

从结果可以看到,越狱版微信没有签名信息。同样,我们使用指令看一下 Store 版本的:

~/Desktop  codesign -vv -d WeChat_Store.app
Executable=/Users/GofLee/Desktop/WeChat_Store.app/WeChat
Identifier=com.tencent.xin
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20500 size=2308891 flags=0x0(none) hashes=36071+7 location=embedded
Signature size=4390
Authority=Apple iPhone OS Application Signing
Authority=Apple iPhone Certification Authority
Authority=Apple Root CA
Info.plist entries=71
TeamIdentifier=88L2Q4487U
Sealed Resources version=2 rules=22 files=1690
Internal requirements count=1 size=96

Store 版的有签名信息,只有砸壳后(参考 《安全攻与防篇:砸壳》,我们才能正常安装到【非越狱设备】上。否则,会安装失败:

3.2 越狱版应用手动重签名

现在我们来讲解一下,使用自己的证书,怎么对越狱版App 进行签名。

通过下面的指令,可以查看本机的所有证书:

~/Desktop  security find-identity -v -p codesigning
  1) 0D0AB1DB51E572818734BEA3B2E3653ACE9DEBC6 "iPhone Developer: 高峰 李 (X5KZZFD3SF)"
  2) 410FCC7179F375AF6EA69845821022D75766EEB8 "iPhone Developer: 高峰 李 (ZC795RL433)"
  3) 21A46572B2F356E4717F57137B8666453D624BAE "iPhone Developer: ligaofeng0927@163.com (UQ6EV7NKNF)"
  4) D7C10359951CA6CCAD146A75C16BD8F4876BFDB4 "iPhone Developer: 高峰 李 (MYVFGJLB74)" (CSSMERR_TP_CERT_REVOKED)
  5) 962553F336E2293011980E92D040E7701BE3C068 "iPhone Distribution: xxxxx Co., Ltd."
  6) 1B065EFA0CDF64317E024F4EB81058DA55896B84 "Apple Development: 高峰 李 (X5KZZFD3SF)"
  7) EAED5E1B94EA730ED062DBBE93A409CC3E7A0BA8 "Apple Development: 高峰 李 (ZC795RL433)"
     7 valid identities found

可以看到上面的证书有很多个,但使用哪一个呢?可以看一下 Xcode 中,运行工程到设备上,使用的是哪一个。

从【Xcode】中可以看到,使用的是最后一个。因此下面的重签名操作,我们也使用最后那个证书。

通过下面的指令,来看一下越狱版微信可执行文件的加密信息:

~/Desktop/WeChat.app  otool -l WeChat | grep crypt
     cryptoff 16384
    cryptsize 55164928
      cryptid 0
     cryptoff 16384
    cryptsize 59457536
      cryptid 0

Store 版微信的加密信息:

 ~/Desktop/WeChat_Store.app otool -l WeChat | grep crypt
     cryptoff 16384
    cryptsize 121225216
      cryptid 1

可以看到,Store 版微信的加密标识为 1,而越狱版微信的加密标识为 0。系统会将加密的应用进行解密,从而我们就可以使用 App。

下面,我们来分析一下:手动重签越狱版微信的相关操作。

  • 第一步:删除插件 Plugins 文件夹里面的内容,因为个人开发者账号,是没法对 Plugins 进行重签名的;
  • 第二步:删除 Watch 文件夹,这里面也有一个 Plugins,并且 Watch 本身我们也不使用;
  • 第三步:对所有的 Frameworks 进行签名(指令中的 -fs 表示强制签名);
~/Desktop/WeChat.app/Frameworks  ls
MMCommon.framework   MultiMedia.framework WCDB.framework       mars.framework
 ~/Desktop/WeChat.app/Frameworks  codesign -fs "Apple Development: 高峰 李 (ZC795RL433)" MMCommon.framework
MMCommon.framework: replacing existing signature
 ~/Desktop/WeChat.app/Frameworks  codesign -fs "Apple Development: 高峰 李 (ZC795RL433)" MultiMedia.framework
MultiMedia.framework: replacing existing signature
 ~/Desktop/WeChat.app/Frameworks  codesign -fs "Apple Development: 高峰 李 (ZC795RL433)" WCDB.framework
WCDB.framework: replacing existing signature
 ~/Desktop/WeChat.app/Frameworks  codesign -fs "Apple Development: 高峰 李 (ZC795RL433)" mars.framework
mars.framework: replacing existing signature
  • 第四步:给可执行文件赋予执行权限;
 ~/Desktop/WeChat.app  chmod +x WeChat
  • 第五步:示例工程(就是纯新建一个 Demo,配置一下证书),在真机编译一下,从生成的 app 文件中,拷贝描述文件 embedded.mobileprovision
  • 第六步:修改 info.plist 中的 Bundle ID(使用 Demo 中的 Bundle ID);
  • 第七步:生成 plist 的权限文件。这里有几个小步骤:
    • 查看第五步中的 embedded.mobileprovision 文件。指令【security cms -D -i embedded.mobileprovision】
    • 拷贝对应的权限字段内容:Entitlements
    • 生成一个 plist 文件(可以在 Xcode 中添加),将刚拷贝的权限字段内容放入 plist。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>

                <key>application-identifier</key>
        <string>C62TJ4568H.*</string>

                <key>keychain-access-groups</key>
        <array>
                <string>C62TJ4568H.*</string>
        </array>

                <key>get-task-allow</key>
        <true/>

                <key>com.apple.developer.team-identifier</key>
        <string>C62TJ4568H</string>

    </dict>
</plist>
  • 第八步:将上一步生成好的 plist 文件放入和 .app 同级目录下,然后对整个 app 进行签名。
~/Desktop  codesign -fs "Apple Development: 高峰 李 (ZC795RL433)" --no-strict --entitlements=Entitlements.plist WeChat.app
WeChat.app: replacing existing signature
  • 第九步:打包。
 ~/Desktop  zip -ry WeChat.ipa Payload
  • 第十步:安装到设备(这里要注意的是:设备上不要有同样 bundle id 的 App,不然会安装失败)。

    至此,我们通过手动的方式,成功的将越狱版微信进行了签名,并安装到【非越狱设备】上。

4.使用 Xcode 重签名

使用 Xcode 重签名,前面 4 个步骤和手动重签名一样,这里我们也完整的写一下相关的步骤,不过上面已经写过的指令,这里就不再重复了。

  • 第一步:删除插件 Plugins 文件夹里面的内容,因为个人开发者账号,是没法对 Plugins 进行重签名的;
  • 第二步:删除 Watch 文件夹,这里面也有一个 Plugins,并且 Watch 本身我们也不使用;
  • 第三步:对所有的 Frameworks 进行签名(指令中的 -fs 表示强制签名);
  • 第四步:给可执行文件赋予执行权限;
  • 第五步:示例工程(就是纯新建一个 Demo,配置一下证书),在真机编译一下,找到生成的 app 文件目录,将刚修改的 Wechat.app 拷贝这个目录下,删除 Demo 生成的 app 文件,并修改 Wechat.app 名称为 Demo 生成的 app 文件名称;
  • 第六步:修改 info.plist 中的 Bundle ID(使用 Demo 中的 Bundle ID);
  • 第七步:真机运行 Demo。

【注意】:要使用 【Legacy Build System】,不然有可能会失败。

5.使用 AppSigner 工具重签名

top Created with Sketch.