iOS App 签名的原理 && App 重签名(三)

目录

iOS App 重签名 - 准备工作

  • iOS App 重签名 简述

    越狱手机已经破解了 iOS 系统的权限,不需要验证 IPA 包的签名,就可以安装任何 IPA 包
    非越狱手机在安装 IPA 包时,iOS 系统会验证 IPA 包的签名

    如果修改了 IPA 包的内容,会导致 IPA 包中原有的签名失效
    此时,IPA 包需要进行重签名后,才能再次安装到非越狱的手机上

    iOS App 重签名的原理,利用的是苹果开发者账号的 Certificate + Provisioning Profile 对 IPA 包进行重新签名
    将已发布的 IPA 包伪装成开发调试的 IPA 包,以达到欺骗 iOS 系统,通过签名验证的目的

    下面以 微信_7.0.14 的 IPA 包为例,介绍 IPA 包的重签名过程
    为了让重签名的过程更加地流畅,在开始重签名之前,需要做以下准备工作:

  • ① 准备 微信_7.0.14 的 IPA 包

    在 Desktop 新建(WeChat IPA 目录),将预先获取到的(微信_7.0.14.ipa)拷贝到(WeChat IPA 目录)中
    在(WeChat IPA 目录)下,再次拷贝(微信_7.0.14.ipa),并将副本重命名为(微信_7.0.14.zip)
    解压(微信_7.0.14.zip),得到(微信_7.0.14 目录)
    最终结果如下图所示:
    iOS App 签名的原理 && App 重签名(三)

  • ② 检查 IPA 包的签名信息 && 验证 IPA 包签名的完整性

    在对 IPA 包进行重签名之前,需要先检查 IPA 包的签名信息 && 验证 IPA 包签名的完整性,确保 IPA 包没有被第三方修改过
    在对 IPA 包进行重签名之后,也需要检查 IPA 包的签名信息 && 验证 IPA 包签名的完整性,确保对 IPA 包的重签名是成功的

    ## 使用 codesign -vv -d WeChat.app 查看 IPA 包的签名信息
    ## 其中参数 -v 代表输出的属性信息的详细程度,有以下 3 个等级:
    ## -v:简略
    ## -vv:中等(一般使用此选项即可)
    ## -vvv:详细
    ## 
    ## 注意:
    ## 1.  codesign -vv -d WeChat.app 用于查看 .app 文件的属性信息,不是用于查看 MachO 文件的属性信息
    ## 2.从 Format 项可以看出,微信的 MachO 是通用二进制文件格式,包含 2 个 CPU 架构的代码(armv7 arm64)
    ~ > cd '/Users/Airths/Desktop/WeChat IPA/微信_7.0.14/Payload'
    ~/Desktop/WeChat IPA/微信_7.0.14/Payload > codesign -vv -d WeChat.app
    Executable=/Users/Airths/Desktop/WeChat IPA/微信_7.0.14/Payload/WeChat.app/WeChat
    Identifier=com.tencent.xin
    Format=app bundle with Mach-O universal (armv7 arm64)
    CodeDirectory v=20200 size=434735 flags=0x0(none) hashes=13578+5 location=embedded
    Signature size=4297
    Authority=Apple iPhone OS Application Signing
    Authority=Apple iPhone Certification Authority
    Authority=Apple Root CA
    Info.plist entries=52
    TeamIdentifier=88L2Q4487U
    Sealed Resources version=2 rules=15 files=776
    Internal requirements count=1 size=96
    

    在检查完 IPA 包的签名信息之后,还需要验证 IPA 包签名的完整性

    ~ > cd '/Users/Airths/Desktop/WeChat IPA/微信_7.0.14/Payload'
    ~/Desktop/WeChat IPA/微信_7.0.14/Payload > codesign --verify WeChat.app
    WeChat.app: resource envelope is obsolete (custom omit rules)
    
  • ③ 检查 IPA 包是否加壳

    因为,加壳的 IPA 包,有加密标识,重签名之后,无法读取,这样会导致不能安装
    所以,iOS App 重签名 都是针对脱壳后的 IPA 包
    注意:

    1. 这里讲的 IPA 包是否加壳,具体指的是 IPA 包中 .app 目录下主工程的 MachO 文件是否加壳
    2. 这里讲的对 IPA 包进行重签名,具体指的是对 IPA 包中的 .app 文件进行重签名
    ## otool(object file displaying tool) : XCode 自带的针对目标文件的展示工具
    ## 注意:
    ## 1. otool 是用于查看 MachO 文件的工具,不是用于查看 .app 文件的工具
    ## 2. otool -l 的输出的信息比较多,可以:
    ##			2.1 将输出信息重定向到 txt 文件里面,方便查看
    ##			2.2 使用 grep 过滤出需要的信息
    ## 3. otool -l 的输出信息中,cryptid 代表的是 MachO 文件的加壳方式,是一个枚举值:
    ##			3.1 cryptid == 0 ,代表 MachO 文件没有加壳
    ##			3.2 cryptid == 1 ,代表 MachO 文件使用枚举值为 1 的方式加壳
    ## 4. 
    ## Question:为什么 otool -l WeChat | grep cryptid 会输出 2 个 cryptid ?
    ## Answer:因为 WeChat 的 MachO 文件是通用二进制格式的,里面包含了 2 种 CPU 架构的二进制代码
    ~ > cd '/Users/Airths/Desktop/WeChat IPA/微信_7.0.14/Payload/WeChat.app'
    ~/Desktop/WeChat IPA/微信_7.0.14/Payload/WeChat.app > otool -l WeChat > ~/Desktop/WeChat_MachO.txt
    ~/Desktop/WeChat IPA/微信_7.0.14/Payload/WeChat.app > otool -l WeChat | grep cryptid
    		cryptid 0
    		cryptid 0
    
  • ④ 准备 Certificate 、BundleID、Provisioning Profile

    关于 Certificate、BundleID、Provisioning Profile 的作用 && 如何使用苹果开发者账号生成 Certificate、BundleID、Provisioning Profile,网上有很多的资料,不清楚的同学请自行百度

    这里着重讲 3 点:

    1. 确保 Certificate、用于调试的 iPhone 的 UDID、Provisioning Profile 这三者是一一对应的,即
      Provisioning Profile 的证书列表中包含 用于重签 IPA 包的 Certificate
      Provisioning Profile 的设备列表中包含 用于调试的 iPhone 的 UDID
    2. 建议 Provisioning Profile 中的 Bundle ID 和 IPA 包中原来的 Bundle ID 一致
    3. 在逆向开发过程中,有可能需要对 IPA 包进行多个版本的重签名
      因此,建议 Provisioning Profile 的 BundleID 选择 苹果开发者账号中默认提供的通配 ID:XC Wildcard(*)

    到苹果开发者账号中,新建符合条件的 Provisioning Profile,并下载到本地
    将 Provisioning Profile 重命名为 embedded.mobileprovision,并拷贝到(WeChat IPA 目录)下
    将(Payload 目录)移动到(WeChat IPA 目录)下,并删除(微信_7.0.14 目录)
    最终结果如下图所示:
    iOS App 签名的原理 && App 重签名(三)

iOS App 重签名 - 手动重签名

  • 进入 Payload 目录,右键 WeChat.app - 显示包内容

  • 删除 WeChat.app 包中的所有插件

    因为个人开发者证书不能重签(PlugIns 目录)里面的插件,所以需要删除(PlugIns 目录)里面的所有插件,保留空的(PlugIns 目录)
    也可以直接删除(PlugIns 目录)

    (Watch 目录)下包含了用于 iWatch 的 WeChatWatchNative.app 包,WeChatWatchNative.app 包中同样有(PlugIns 目录),里面的插件也需要删除
    因为 iWatch 版的微信很少使用,所以也可以直接删除整个(Watch 目录)
    iOS App 签名的原理 && App 重签名(三)

  • 修改 Info.plist 文件

    因为是使用 embedded.mobileprovision 对 WeChat.app 进行重签名
    所以需要将 WeChat.app 中的 Info.plist 的 BundleID 改成 embedded.mobileprovision 中的 BundleID

    因为 embedded.mobileprovision 中使用的是苹果开发者账号提供的通配ID:XC Wildcard(*),可以匹配任意的 BundleID
    所以可以不用修改 WeChat.app 中的 Info.plist 的 BundleID

    因为用于真机调试的 iPhone 上,已经装有正版的微信,存在 BundleID:com.tencent.xin
    又因为一台 iOS 设备上,不能存在两个相同的 BundleID
    所以,将 WeChat.app 中的 Info.plist 的 BundleID 修改为:com.hcg.xin
    注意:自定义的 BundleID 要合法
    iOS App 签名的原理 && App 重签名(三)

  • 为 MachO 文件添加可执行权限

    主工程的 MachO 文件需要有可执行权限,否则,App 安装后无法运行

    ~ > cd '/Users/Airths/Desktop/WeChat IPA/Payload/WeChat.app'
    ~/Desktop/WeChat IPA/Payload/WeChat.app > chmod a+x WeChat
    ~/Desktop/WeChat IPA/Payload/WeChat.app > ls -l WeChat
    -rwxr-xr-x  1 Airths  staff  161060640  7  8 15:43 WeChat
    
  • 重签名 Frameworks 目录下的所有 framework

    因为,每个 framework 其实就是一个 App,有自己独立的签名信息
    所以,在进行 iOS App 重签名时,需要对 Frameworks 目录下的每一个 framework 都进行强制重签名

    ## 进入到 Frameworks 目录
    ~ > cd '/Users/Airths/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks'
    ## 列出 Frameworks 目录下的所有 framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > ls
    andromeda.framework      
    mars.framework   
    marsbridgenetwork.framework
    matrixreport.framework
    OpenSSL.framework           
    ProtobufLite.framework      
    ## 对每个 framework 强制重签名
    ## andromeda.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" andromeda.framework
    andromeda.framework: replacing existing signature
    ## mars.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" mars.framework
    mars.framework: replacing existing signature
    ## marsbridgenetwork.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" marsbridgenetwork.framework
    marsbridgenetwork.framework: replacing existing signature
    ## matrixreport.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" matrixreport.framework
    matrixreport.framework: replacing existing signature
    ## OpenSSL.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" OpenSSL.framework
    OpenSSL.framework: replacing existing signature
    ## ProtobufLite.framework
    ~/Desktop/WeChat IPA/Payload/WeChat.app/Frameworks > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" ProtobufLite.framework
    ProtobufLite.framework: replacing existing signature
    
  • 从 Provisioning Profile 中抽取 entitlements 文件

    因为是使用 embedded.mobileprovision 对 WeChat.app 进行重签名
    所以重签名后的 WeChat.app 的应用权限需要跟 embedded.mobileprovision 中的应用权限保持一致
    这里将 embedded.mobileprovision 中的应用权限导出为一个单独的 plist 文件,为下一步重签名整个 WeChat.app 包做准备

    ~ > cd '/Users/Airths/Desktop/WeChat IPA'
    ## 将 embedded.mobileprovision 导出为 temp.plist
    ~/Desktop/WeChat IPA > security cms -D -i embedded.mobileprovision > temp.plist
    ## 将 temp.plist 里面的权限字段导出为 entitlements.plist
    ~/Desktop/WeChat IPA > /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist
    ## 查看 entitlements.plist
    ~/Desktop/WeChat IPA > cat entitlements.plist
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>application-identifier</key>
    	<string>8H77R3JUK7.*</string>
    	<key>com.apple.developer.team-identifier</key>
    	<string>8H77R3JUK7</string>
    	<key>get-task-allow</key>
    	<true/>
    	<key>keychain-access-groups</key>
    	<array>
    		<string>8H77R3JUK7.*</string>
    		<string>com.apple.token</string>
    	</array>
    </dict>
    </plist>
    
  • 通过 entitlements 文件重签名整个 WeChat.app 包

    ~ > cd '/Users/Airths/Desktop/WeChat IPA'
    ~/Desktop/WeChat IPA > codesign -fs "iPhone Developer: chaogen huang (VGS95QQ774)" --no-strict --entitlements=entitlements.plist Payload/WeChat.app
    Payload/WeChat.app: replacing existing signature
    
  • 将 Payload 目录打包成 IPA 文件

    重签名后的 .app 无法安装,需要将其打包成 .ipa 才能安装

    ## zip 命令执行后,会输出 WeChat.app 下所有资源的 adding 信息
    ## 请耐心等待打包过程结束
    ~ > cd '/Users/Airths/Desktop/WeChat IPA'
    zip -ry ReSignature.ipa ./Payload
    
  • 删除重签名过程产生的中间文件
    iOS App 签名的原理 && App 重签名(三)

  • 通过 XCode 安装重签名后的 IPA 包

    因为正版微信中往往存储着聊天记录等重要数据
    所以建议新手同学先备份正版微信的数据,再进行重签名 IPA 包的真机测试

    将用于真机测试的 iPhone 连接上 MacBook
    打开 XCode - Window - Devices and Simulators(Command + Shift + 2)
    找到用于用于真机测试的 iPhone - INSTALLED APPS - 加号(+) - 选择 IPA 包 - 等待安装完成
    注意:安装过程中,需要保持 iPhone 为解锁状态
    iOS App 签名的原理 && App 重签名(三)

iOS App 重签名 - 使用脚本重签名

  • 重签名脚本的简介

    上面手动重签名的过程,可以使用下面的 Bash 脚本自动完成

    #!/bin/bash
    # Date:         14:56 2017-06-07
    # Mail:         994923259@qq.com
    # Author:       Created by Airths
    # Function:     This script is for resigning an .IPA package
    # Version:      1.0
    # Return:       0 for success, 1 for failure
    # Description:
    #   1.将 provisioning profile 和 IPA 包放到同一个目录下(将此目录定义为基础目录)
    #   2.请根据实际情况,修改用户变量里面的值
    
    # pragma -mark 用户变量
    # 基础目录路径:用于存放 provisioning profile 和 IPA 包
    baseDirPath="/Users/Airths/Desktop/IPA_ReSignature"
    
    # 证书名称
    cerName="iPhone Developer: chaogen huang (VGS95QQ774)"
    
    # provisioning profile 的名称(包含 .mobileprovision 扩展名)
    ppName="embedded.mobileprovision"
    
    # IPA 包名称(包含 .ipa 扩展名)
    ipaName="WeChat.ipa";
    
    # BundleID(建议为域名反写的形式)
    bundleID="com.hcg.xin";
    
    
    
    # pragma -mark 断言
    # 判断基础目录是否存在
    if [ ! -d "${baseDirPath}" ]
    then
        echo "基础目录:${baseDirPath} 不存在"
        echo "请检查基础目录路径是否正确"
        exit 1
    fi
    
    # 判断钥匙串中是否包含指定证书
    cerList=`security find-identity -v -p codesigning`
    cerFlag=$(echo "${cerList}" | grep "${cerName}")
    if [ "${cerFlag}" == "" ]
    then
        echo "Keychain Access 中不存在证书:"
        echo "${cerName}"
        echo "请检查证书名称是否正确"
        exit 1
    fi
    
    # 判断 provisioning profile 扩展名是否正确
    if [[ "${ppName}" != *.mobileprovision ]]
    then
        echo "provisioning profile 正确的扩展名为:"
        echo ".mobileprovision"
        echo "请检查 provisioning profile 的扩展名是否正确"
        exit 1
    fi
    
    # 判断基础目录下是否存在 provisioning profile
    ppPath="${baseDirPath}/${ppName}"
    if [ ! -f "${ppPath}" ]
    then
        echo "路径:${baseDirPath} 下"
        echo "不存在名为:${ppName} 的 provisioning profile"
        echo "请检查  provisioning profile 的名称是否正确"
        exit 1
    fi
    
    # 判断 IPA 包的扩展名是否正确
    if [[ "${ipaName}" != *.ipa ]]
    then
        echo "IPA 包的正确的扩展名为:"
        echo ".ipa"
        echo "请检查 IPA 包的扩展名是否正确"
        exit 1
    fi
    
    # 判断基础目录下是否存在 IPA 包
    ipaPath="${baseDirPath}/${ipaName}"
    if [ ! -f "${ipaPath}" ]
    then
        echo "路径:${baseDirPath} 下"
        echo "不存在名为:${ipaName} 的 IPA 包"
        echo "请检查 IPA 包的名称是否正确"
        exit 1
    fi
    
    # 判断 BundleID 是否为空
    if [ -z "${bundleID}" ]
    then
        echo "Bundle ID 非法"
        echo "Bundle ID 不能包含中文且不能为空"
        echo "建议 Bundle ID 为域名反写的形式"
        exit 1
    fi
    
    
    
    # pragma -mark 执行重签名
    # 0.创建 temp 目录,并将 IPA 包解压到 temp 目录下
    tempDirPath="${baseDirPath}/temp"
    if [ -d "${tempDirPath}" ]
    then
        rm -rf "${tempDirPath}"
    fi
    mkdir "${tempDirPath}"
    unzip -oqq "${ipaPath}" -d "${tempDirPath}"
    
    # > 进入 .app 目录
    appDirPath=$(set -- "${tempDirPath}/Payload/"*.app; echo "$1")
    cd "${appDirPath}"
    
    # 1.删除 .app 包中的所有插件
    # 1.1删除 PlugIns 目录
    plugInsDirPath="${appDirPath}/PlugIns"
    if [ -d "${plugInsDirPath}" ]
    then
        rm -rf "${plugInsDirPath}"
    fi
    # 1.2删除 Watch 目录
    watchDirPath="${appDirPath}/Watch"
    if [ -d "${watchDirPath}" ]
    then
        rm -rf "${watchDirPath}"
    fi
    
    # 2.修改 Info.plist 文件的 BundleID
    # BundleID 需要合法 && 不能和 iPhone 上已有的 Bundle ID 相同
    /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${bundleID}" info.plist
    
    # 3.为 MachO 文件添加可执行权限
    appName=`echo ${appDirPath##*/}`    # WeChat.app
    machoName=`echo ${appName%.*}`      # WeChat
    chmod a+x "${machoName}"
    
    # 4.重签名 Frameworks 目录下的所有 framework
    frameworksDirPath="${appDirPath}/Frameworks"
    # 4.1判断 Frameworks 目录是否存在
    if [ -d "${frameworksDirPath}" ]
    then
        # > 进入 Frameworks 目录
        cd "${frameworksDirPath}"
        # 4.2判断 Frameworks 目录下是否存在 framework
        fmwkList=$1
        if [ "`ls -A ${fmwkList}`" != "" ]
        then
            # 4.3重签名 Frameworks 目录下的所有 framework
            for frameworkName in `ls`
            do
                codesign -fs "${cerName}" "${frameworkName}"
            done
        fi
    fi
    
    # > 退回到基础目录
    cd "${baseDirPath}"
    
    # 5.从 Provisioning Profile 中抽取 entitlements 文件
    security cms -D -i "${ppName}" > temp.plist
    /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' temp.plist > entitlements.plist
    
    # 6.通过 entitlements 文件重签名整个 .app 包
    codeSign -fs "${cerName}" --no-strict --entitlements=entitlements.plist "${appDirPath}"
    
    # 7.生成新的 IPA 包
    mkdir Payload
    mv "${appDirPath}" ./Payload
    zip -ry ReSignature.ipa Payload
    
    if(($?==0))
    then
        echo "重签名成功"
        echo "签名前的 IPA 包:${ipaName}"
        echo "签名后的 IPA 包:ReSignature.ipa"
    	# 8.删除重签名过程中产生的中间文件
    	rm -rf Payload
    	rm -rf "${tempDirPath}"
    	rm -rf temp.plist
    	rm -rf entitlements.plist
    	# 返回成功
    	exit 0
    else
        echo "重签名失败"
        echo ".app 打包失败"
    	# 8.删除重签名过程中产生的中间文件
    	rm -rf Payload
    	rm -rf "${tempDirPath}"
    	rm -rf temp.plist
    	rm -rf entitlements.plist
    	# 返回失败
    	exit 1
    fi
    
  • 重签名脚本的使用

    关于 IPA 包、Certificate 、BundleID、Provisioning Profile 的注意事项,请参照 iOS App 重签名 - 准备工作 中的注意事项

    重签名脚本的使用过程如下:

    1. 新建脚本文件 ReSignature.sh,将上面的 Bash 脚本代码复制到 ReSignature.sh 并保存
    2. 新建(IPA_ReSignature 目录),将 ReSignature.sh、WeChat.ipa、embedded.mobileprovision 复制到(IPA_ReSignature 目录)
    3. 运行 macOS 命令行工具,进入(IPA_ReSignature 目录),给与 ReSignature.sh 可执行权限
    4. 通过 Bash 命令执行 ReSignature.sh

    流程如下图所示:
    iOS App 签名的原理 && App 重签名(三)

    ~ > cd /Users/Airths/Desktop/IPA_ReSignature
    ~/Desktop/IPA_ReSignature > chmod a+x ReSignature.sh
    ~/Desktop/IPA_ReSignature > ls -l ReSignature.sh
    -rwxr-xr-x@ 1 Airths  staff  5279  8 12 13:53 ReSignature.sh
    ~/Desktop/IPA_ReSignature > bash ReSignature.sh
    

iOS App 重签名 - 使用 XCode 重签名

  • 使用 XCode 重签名的简介

    上面介绍的 2 种 iOS App 重签名的方式:
    ① 手动重签名
    ② 使用脚本重签名
    可以直接生成 IPA 包,适合对逆向开发完成的 App 进行重签名

    但是在逆向开发过程中,需要频繁地对 App 进行分析、更改和调试
    如果每次用 XCode 对 App 做完更改之后,都使用上面的方式进行重签名、打包、然后真机调试
    将会浪费巨量的时间在 App 的重签名和打包上面

    因此,我们需要一种能将对 App 的重签名集成到 XCode 上的方式
    方便我们在 XCode 上对 App 进行分析和更改之后,快速地进行重签名、安装、调试

  • 使用 XCode 重签名的流程

  1. 新建工程 CodeSignature.project
  2. 在 CodeSignature.project 的根目录下新建(IPA目录),将需要重签名的 IPA 包放到该目录下
    iOS App 签名的原理 && App 重签名(三)
  3. 根据需要,选择工程的开发者团队并设置工程的 BundleID,然后 Command + B 编译工程
    iOS App 签名的原理 && App 重签名(三)
  4. 在 XCode 工程中添加一个脚本:Target - Build Phases - 加号(+) - New Run Script Phase
    注意:
    ① 脚本将在工程编译前执行
    ② 如果是使用下图的方式 2 - 通过路径添加脚本,建议将脚本一同放到工程根目录下,此时脚本路径为:
    ${SRCROOT}/ReSignature.sh
    iOS App 签名的原理 && App 重签名(三)
  5. 这里采用直接在 XCode 中编写 Bash 代码的方式新建脚本,脚本代码如下:
    #!/bin/bash
    # Date:         16:32 2017-06-07
    # Mail:         994923259@qq.com
    # Author:       Created by Airths
    # Function:     This script is for resigning an .IPA package in XCode
    # Version:      1.0
    # Return:       0 for success, 1 for failure
    # Description:	
    #
    # XCode Enviroment Variables:
    #   SRCROOT - 工程根目录的路径
    #   BUILT_PRODUCTS_DIR - 工程编译生成的 .app 包的路径
    #   TARGET_NAME - 工程名称 / Target 名称
    #   PRODUCT_BUNDLE_IDENTIFIER - 工程的 BundleID
    #   EXPANDED_CODE_SIGN_IDENTITY 工程的证书
    
    # pragma -mark 用户变量
    # 要注入的 Framework 的名称(不包含后缀名)
    # 填空则代表不进行 Framework 注入
    frameworkName=""
    
    
    
    # pragma -mark 断言
    # 判断 framework 是否包含后缀
    if [[ "${frameworkName}" = *.farmework ]]
    then
        echo "frameworkName=${frameworkName}"
        echo "请不要包含 .framework 扩展名"
        exit 1
    fi
    
    
    
    # pragma -mark 执行重签名
    # 新建 IPA 目录
    ipaDirPath="${SRCROOT}/IPA"
    if [ ! -d "${ipaDirPath}" ]
    then
        mkdir "${ipaDirPath}"
    fi
    
    # 检查 IPA 文件是否存在
    ipaPath=$(set -- "${ipaDirPath}/"*.ipa; echo "$1")
    if [ ! -f "${ipaPath}" ]
    then
        echo "未检测到 IPA 包"
        echo "请将 IPA 包手动拷贝到目录:"
        echo "${ipaDirPath}"
        exit 1
    fi
    
    # 新建 Temp 目录
    tempDirPath="${SRCROOT}/Temp"
    if [ -d "${tempDirPath}" ]
    then
    	rm -rf "${tempDirPath}"
    fi
    mkdir "${tempDirPath}"
    
    # 1.将 IPA 包解压到 Temp 目录下并获取 .app 包的路径
    unzip -oqq "${ipaPath}" -d "${tempDirPath}"
    sourceAppDirPath=$(set -- "${tempDirPath}/Payload/"*.app; echo "$1")
    
    # 2.替换工程中的 .app 包
    targetAppDirPath="${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app"
    rm -rf "${targetAppDirPath}"
    cp -rf "${sourceAppDirPath}" "${targetAppDirPath}"
    echo "sourceAppDirPath = ${sourceAppDirPath}"
    echo "targetAppDirPath = ${targetAppDirPath}"
    
    # 3.删除 .app 包中的所有插件
    # 3.1删除 PlugIns 目录
    plugInsDirPath="${targetAppDirPath}/PlugIns"
    if [ -d "${plugInsDirPath}" ]
    then
        rm -rf "${plugInsDirPath}"
    fi
    # 3.2删除 Watch 目录
    watchDirPath="${targetAppDirPath}/Watch"
    if [ -d "${watchDirPath}" ]
    then
        rm -rf "${watchDirPath}"
    fi
    
    # 4.修改 Info.plist 文件的 Bundle ID
    infoPlistPath="${targetAppDirPath}/Info.plist"
    /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER}" "${infoPlistPath}"
    
    # 5.为 MachO 文件添加可执行权限
    machOName=`plutil -convert xml1 -o - $targetAppDirPath/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
    machOPath="${targetAppDirPath}/${machOName}"
    chmod a+x "${machOPath}"
    
    # 6.重签名 Frameworks 目录下的所有 framework
    frameworksDirPath="${targetAppDirPath}/Frameworks"
    # 6.1判断 Frameworks 目录是否存在
    if [ -d "${frameworksDirPath}" ]
    then
        # > 进入 Frameworks 目录
        cd "${frameworksDirPath}"
        # 6.2判断 Frameworks 目录下是否存在 framework
        fmwkList=$1
        if [ "`ls -A ${fmwkList}`" != "" ]
        then
            # 6.3重签名 Frameworks 目录下的所有 framework
            for frameworkName in `ls`
            do
                codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" "${frameworkName}"
            done
        fi
    fi
    
    # 7.使用 yololib 进行代码注入 - Framework 注入
    if [ ! -z "${frameworkName}" ]
    then
        yololib "${machOPath}" "Frameworks/${frameworkName}.framework/${frameworkName}"
    fi
    
  6. 编写完脚本之后,将调试用的 iPhone 连接到 MacBook 上,在 XCode 中选择调试用的真机
    Command + Shift + K,清空工程的 Build 文件夹(编译结果文件夹)
    Command + B,编译工程
    Command + R,将重签名后的 App 安装到调试用的 iPhone 上
    注意:在安装过程中,需要保持 iPhone 为解锁状态
  • 使用 XCode 重签名的注意事项:
  1. 配置的 Bash 脚本将在工程编译之前执行
  2. 每次编译和运行工程,配置的 Bash 脚本都会执行一次,如果需要解压效果不同,需要对原 IPA 包做更改
  3. 建议直接将脚本写在工程中,方便查错和更改
    同时将脚本保存一份,每次新建工程时,从预先准备的脚本中复制代码到工程里面即可
  4. XCode 编译之后,可以使用快捷键:Command + 9,查看脚本执行后输出的结果
  • 相对于使用脚本重签名,在使用 XCode 重签名的过程中,XCode 自动完成了以下步骤:
  1. XCode 会根据在工程中配置的苹果开发者账号、BundleID、Capabilities,连接的 iPhone 的 UDID 等信息
    自动到苹果开发者网站注册 AppID、注册 设备 ID、申请 Certificate、申请 Provisioning Profile
    然后将 Certificate 和 Provisioning Profile下载到本地,并应用到工程中
  2. 在 XCode 中使用 Command + R 进行真机调试时
    XCode 会自动从工程的 Provisioning Profile 中抽取 entitlements.plsit(权限文件)
    然后使用 entitlements.plsit 和 Certificate 对 Product 下的 .app 包进行重签名
    并将 .app 包安装到真机上

注意

  • otool 常用命令简介

    在终端输入 otool,可以查看 otool 的常用命令:

    ~ > otool
    Usage: /Library/Developer/CommandLineTools/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
    	-f print the fat headers
    	-a print the archive header
    	-h print the mach header
    	-l print the load commands
    	-L print shared libraries used
    	-D print shared library id name
    	-t print the text section (disassemble with -v)
    	-x print all text sections (disassemble with -v)
    	-p <routine name>  start dissassemble from routine name
    	-s <segname> <sectname> print contents of section
    	-d print the data section
    	-o print the Objective-C segment
    	-r print the relocation entries
    	-S print the table of contents of a library (obsolete)
    	-T print the table of contents of a dynamic shared library (obsolete)
    	-M print the module table of a dynamic shared library (obsolete)
    	-R print the reference table of a dynamic shared library (obsolete)
    	-I print the indirect symbol table
    	-H print the two-level hints table (obsolete)
    	-G print the data in code table
    	-v print verbosely (symbolically) when possible
    	-V print disassembled operands symbolically
    	-c print argument strings of a core file
    	-X print no leading addresses or headers
    	-m don't use archive(member) syntax
    	-B force Thumb disassembly (ARM objects only)
    	-q use llvm's disassembler (the default)
    	-Q use otool(1)'s disassembler
    	-mcpu=arg use `arg' as the cpu for disassembly
    	-j print opcode bytes
    	-P print the info plist section as strings
    	-C print linker optimization hints
    	--version print the version of /Library/Developer/CommandLineTools/usr/bin/otool
    
  • 完全重签名 && 不完全重签名

    在对 IPA 包进行重签名时,需要预先准备:
    ① Certificate
    ② Provisioning Profile(包含证书列表,设备列表,权限列表,AppID -> BundleID)
    同时需要保证:
    ① Certificate 在 Provisioning Profile 的证书列表当中
    ② IPA 包的 BundleID 需要与 Provisioning Profile 中的 BundleID 保持一致。如果 IPA 包的原 BundleID 与 Provisioning Profile 的 BundleID 不一致,则需要修改 IPA 包中 Info.plist 的 BundleID

    根据 Provisioning Profile 中的 BundleID 与 IPA 包的原 BundleID 是否一致,将 iOS App 重签名分为:

    ① 完全重签名: Provisioning Profile 中的 BundleID 与 IPA 包的原 BundleID 一致,这种重签名方式基本上与直接对代码进行签名的效果是一样的
    优点:重签名的有效期长、重签名后 App 的稳定性高
    缺点:需要为每一个重签名的 IPA 包创建专属的 Provisioning Profile

    ② 不完全重签名:Provisioning Profile 中的 BundleID 与 IPA 包的原 BundleID 不一致,需要修改 IPA 包中 Info.plist 文件的 BundleID
    优点:同一个 Provisioning Profile 可以签名不同的 IPA 包
    缺点:没有对原 IPA 包的签名进行完全替换(很难保证 IPA 包中的业务逻辑代码没有用到原 BundleID),稳定性较差

    总结:
    在逆向开发过程中,有可能需要对 IPA 包进行多个版本的重签名,为了提升开发效率,建议 Provisioning Profile 的 BundleID 选择 苹果开发者账号 中默认提供的通配 ID:XC Wildcard(*)
    在逆向开发完成后,需要对 App 进行重签名以发布新的 IPA 包,为了保证重签名后 App 的稳定性,建议 Provisioning Profile 的 BundleID 与 IPA 包的原 BundleID 保持一致

  • 重签名工具 codesign 的一些细节

    # 对未签名的 App 手动签名,使用如下命令:
    codesign -s #"证书串"# #.app包路径#
    
    # 对已签名的 App 重新签名,实际上是强制替换 App 中已经存在的签名,使用如下命令
    codesign -f -s #"证书串"# #.app包路径#
    
    # 其中:
    # -s(-sign,签名的意思)
    # -f(-force,强制的意思)
    
  • 关于预置 Provisioning Profile 到 iOS 系统里面

    iOS App 重签名的原理,利用的是苹果开发者账号的 Certificate + Provisioning Profile 对 IPA 包进行重新签名
    将已发布的 IPA 包伪装成开发调试的 IPA 包,以达到欺骗 iOS 系统,通过签名验证的目的

    在正常的真机调试过程中,如果指定的 Provisioning Profile 是第一次安装到某个 iOS 设备 ,则需要到该 iOS 设备的设置中,信任指定的 Provisioning Profile 后,App 才可以正常运行和调试

    在通过 Provisioning Profile 重签名 IPA 包后
    如果该 Provisioning Profile 还未预置到 iOS 系统中(此 Provisioning Profile 还未通过 XCode 以正常真机调试的方式安装到 iOS 系统里面),此时就无法通过 iOS 系统的设置,点击信任此 Provisioning Profile
    那么在 IPA 包安装的时候,可能会出现以下两种情况:
    ① IPA 包安装过程报错
    ② IPA 包安装成功但是 App 不能运行

    解决的方法是:通过 XCode 新建一个使用该 Provisioning Profile 的 Demo 工程,先运行一次 Demo 工程到真机上(即先在 iOS 系统里面安装指定的 Provisioning Profile),再到 iOS 系统的设置里面点击信任该 Provisioning Profile ,然后再安装重签名后的 IPA 包

  • iPhone 通过 USB 数据线连接到 MacBook 后频繁掉线 / 重连

    在真机调试的过程中,遇到了一个郁闷的问题 – iPhone 通过 USB 数据线连接到 MacBook 后频繁掉线 / 重连,具体表现为:
    ① iPhone 状态栏上电池充电标志频繁闪烁,一直在充电与正常状态间切换
    ② iPhone 界面上频繁弹出是否信任此电脑的系统弹窗
    ③ 到 Finder 中查看 iPhone 的连接情况,发现位置条目下的 iPhone 频繁掉线 / 重连
    ④ 到 XCode - Window - Devices and Simulators 查看 iPhone 的连接情况,发现 iPhone 频繁地掉线线 / 重连

    这一切的现象,怎么看都像是硬件出了问题,在做了以下尝试后,仍然解决不了问题:
    ① 更换不同的原装 USB 数据线进行连接
    ② 确认 iPhone 的 USB 接口没有生锈或损坏
    ③ 确认 MacBook 的 USB 接口没有生锈或损坏

    最后,冷静下来,再次耐心地分析了下问题:
    如果是硬件上的损坏,应该表现为 iPhone 与 MacBook 的连接时断时续
    或者 iPhone 干脆就连接不上 MacBook 了
    而不是像现在这样,iPhone 与 MacBook 间频繁地掉线 / 重连,这会不会是 iPhone 与 MacBook 连接的时候,触发了某种验证或者保护机制,最后往这方面查资料,问题得到了解决:
    iPhone 通过 USB 接口连接 MacBook 后频繁闪跳其实是电脑电压的原因,进入终端,输入以下命令,关闭相应的进程后,iPhone 就可以正常连接到 MacBook 了:

    ~ > sudo killall -STOP -c usbd
    
上一篇:如何从零设计一种物联网组网协议


下一篇:关于Certificate、Provisioning Profile