[TOC]
先来学习下三个重要概念:
public/private keys, entitlements, and provisioning profiles.
entitlements是嵌入在App里的XML字符串,表示App可以做什么不可以做什么
App里的provisioning profile文件包含entitlements、可以安装到的真机列表、用来验证签名的public key
Public/private keys
Requesting a Certificate From A Certificate Authority
You created a public/private key, sent up the public key to Apple servers (by the .csr file).
You can view the names, or identities, of your public/private key pairs used for signing your applications with the following Terminal command:
security find-identity -p codesigning -v
This command queries the macOS system keychain, looking for valid identities that contain a private key (-v) and whose type can codesign (-p codesigning).
This ouput will display identities that are valid, which can produce a code signed application. If you look for identities that contain the phrase “iPhone Developer], it’s likely that this identity can be used to sign an iOS application on your device.
➜ bin security find-identity -p codesigning -v
1) D1F90756076CA6CD2495ED5798E146E2426300C0 "iPhone Distribution: Kun Li (2QNTW3GT23)"
2) 6B9065E80E180DD48807C09F92E4D505812E029C "iPhone Developer: [email protected] (TCDLVFEQHJ)"
2 valid identities found
Never, ever, delete a private key!
you can export the public certificates using the following command:
security find-certificate -c "iPhone Developer: Derek Selander (8AW8QLCX5U)" -p
This will output the public, x509 certificate of iPhone Developer: Derek Selander (8AW8QLCX5U) to stdout and format it in PEM format.
Use the Terminal command to cat this newly created file:
cat /tmp/public_cert.cer
-----BEGIN CERTIFICATE----MIIFnDCCBISgAwIBAgIIFMKm2AG4HekwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
...
This is how you can tell this certificate is in PEM.
From here, you can use the openssl Terminal command to query the public, x509 certificate:
openssl x509 -in /tmp/public_cert.cer -inform PEM -text -noout
- The x509 option says that the openssl command should be able to work with a x509 certificate.
- You provide the -in to the path of public_cert.cer with the decoding format of PEM (-inform PEM).
- You specify you don’t want to output a certificate with the -noout param.
- But instead, you do want the certificate in a (somewhat) readable “text” format with the -text option.
There’s two ways to display a certificate: DER and PEM.
PEM can be read by the Terminal (since it’s in base64 encoding)
while DER, in highly professional coding terms, will produce gobbledygook and make the Terminal beep a lot.
Entitlements
Embedded in (almost) every compiled application is a set of entitlements: again, this is an XML string embedded in the application saying what an app can and can’t do.
For example, App Groups, iCloud Services, Push Notifications, Associated Domains all will modify the entitlments to your app. These capabilities shown in Xcode are but a small piece of the entitlements on Apple platforms as the majority of them are private to Apple and enforced through code signing.
重点:
Probably the most important entitlement, at least in this book, is the get-task-allow entitlement, found on all your software compiled with a developer certificate. This allows the program in question to be attached to a debugger.
You can view the entitlements of an application through the codesign Terminal command.
Find the entitlements of the macOS Finder application:
codesign -d --entitlements :- /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
- The -d option says to display the option immediately following in the command, which is the --entitlements.
- The - says to print to stdout
- The : says to omit the blob header and length.
Provisioning profiles
A provisioning profile includes the public x509 certificate, the list of approved devices, as well as the entitlements all embedded into one file.
provisioning profiles默认位置:
~/Library/MobileDevice/Provisioning Profiles/
你和Xcode创建的provisioning profiles以UUID作为文件名,比如fe0c62fd-0f18-4974-821b-84b3337b0c12.mobileprovision
你可以通过security命令查看.mobileprovision文件的内容:
PP_FILE=$(ls ~/Library/MobileDevice/Provisioning\ Profiles/ *mobileprovision | head -1)
security cms -D -i "$PP_FILE"
the security command which decodes (-D) the cryptographic message syntax (cms) format of the provisioning profile, specifying the input path via the -i option.
- AppIDName This is the name of the Application ID that is tied to this provisioning profile
- TeamIdentifier the unique team ID Apple has given me for my team identity
- Entitlements contains the Entitlements of what the app can and can’t do with this signature.
- ProvisionedDevices contains an array of approved devices this provisioning profile can install on, given by a device’s UDID.
- DeveloperCertificates is an array that contains base64-encoded x509 certificates.
- IsXcodeManaged is a Boolean value that indicates if Xcode manages this provisioning profile.
- Name which is the name of the provisioning profile that Apple displays to identify the provisioning profiles on https:// developer.apple.com/account/ios/profile/limited.
探索WordPress app
apple商店上下载的ipa是经过加壳的,不能用于重签名。
这里以开源App:Wordpress v10.9 来重签名:
下载地址:https://github.com/wordpress-mobile/WordPress-iOS/releases/tag/10.9
也可以从像PP助手上下载越狱过的App来重签名
The provisioning profile
APP_FILE="/full/path/to/WordPress.app"
security cms -D -i "$APP_FILE/embedded.mobileprovision"
在这个provisioning profile,包含了如下信息:
- Apple给Automattic, Inc.公司的team identifier为 3TMU3BH3NK.
- Wordpress app使用了iCloud服务, 因为在entitlements dictionary可以看到com.apple.developer.icloud*等keys. It also looks to make use of certain extension like "App Groups".
- get-task-allow 是 false, 调试器不能attach, 不能调试
DeveloperCertificates键的值为x509 证书base64编码后的值
CERT_DATA=MIIFozCCBIu...
解码后保存到/tmp/wordpress_cert.cer:
echo "$CERT_DATA" | base64 -D > /tmp/wordpress_cert.cer
通过openssl命令查看证书内容:
openssl x509 -in /tmp/wordpress_cert.cer -inform DER -text -noout
输出如下:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 786948871528664923 (0xaebcdd447dc4f5b)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
Validity
Not Before: Jan 17 13:26:41 2018 GMT
Not After : Jan 17 13:26:41 2019 GMT
Subject: UID=PZYM8XX95Q, CN=iPhone Distribution: Automattic, Inc. (PZYM8XX95Q), OU=PZYM8XX95Q, O=Automattic, Inc., C=US
可以看出在“Automattic, Inc”工作的某人用“iPhone Distribution: Automattic, Inc. (PZYM8XX95Q)”签名了这个App
Embedded executables
除主应用外,还包含extensions(share extension, today widgets, 和其它等),./Plugins文件夹里的每个extension都有自己的application identifier和embedded.mobileprovision、签名
_CodeSignature文件夹
在真机的app中有一个_CodeSignature文件夹,里面只包含一个叫CodeResources文件。
它是一个XML plist文件,内容为***.app内的每个非可执行文件的checksum值
可以通过openssl命令自己来计算文件的checksum值:
openssl sha1 -binary "$APP_FILE/AboutViewController.nib" | base64
Apple已开始为Xcode 10创建的ios app从SHA-1 checksums过渡到SHA-256 checksums
重点:
CodeResources文件自身的checksum被嵌入到可执行文件中。这意味着在xxx.app中修改任一文件,甚至增加一个文件夹,不重新签名的话,app都会安装失败。
重签名app
需要做如下的步骤:
- 复制有效的provisioning profile文件到xxx.app为embedded.mobileprovision文件
- 修改Info.plist的CFBundleIdentifier键的值为provisioning profile文件中包含的application identifier的值.
- 以合适的entitlements和provisioning profile中包含的开发者身份来重签名App(合适的entitlements从provisioning profile文件里提取)
你能从
~/Library/MobileDevice/Provisioning Profiles/
或
https://developer.apple.com/
上得到provisioning profile文件
(Optional)生成有效的provisioning profile文件
替换provisioning profile文件
复制mobileprovision文件为app内的embedded.mobileprovision
PP_PATH=~/Downloads/ProvisProfile_92618.mobileprovision
cp "$PP_PATH" "$APP_FILE/embedded.mobileprovision"
删除Plugins、Watch等文件夹
app里如果有Plugins文件夹时,里面的每个扩展都有自己唯一的application identifier、唯一的provisioning profile。你能用其他唯一的provisioning profile重签名每个扩展,但作为demo,这样做太复杂了,我们选择删除整个Plugins文件夹。
删除这些文件夹后,重签名后的app就没有相应的功能了,但作为演示可以接受。
修改Info.plist文件
可以从provisioning profile提取到application identifier:
security cms -D -i "$PP_PATH" | grep application-identifier -A1
上面的命令得到如下输出:
application-identifier
H4U46V6494.com.selander.code-signing
通过替换Info.plist中CFBundleIdentifier键的值来修改app的application identifier:
plutil -replace CFBundleIdentifier -string H4U46V6494.com.selander.codesigning "$APP_FILE/Info.plist"
修改WordPress app显示的名字:
plutil -replace CFBundleDisplayName -string "Woot" "$APP_FILE/Info.plist"
提取entitlements
因为entitlements在provisioning profile不是XML,而是字典,为了简单起见:从可执行文件中提取原来entitlements并保存到entitlements.xml文件,再从provisioning profile中提取新的entitlements并替换掉entitlements.xml文件中原来的entitlements
- 从可执行文件中提取entitlements并保存到文件:
codesign -d --entitlements :/tmp/entitlements.xml "$APP_FILE/WordPress"
cat /tmp/entitlements.xml
- 从provisioning profile生成新的entitlements.xml的模板:
security cms -D -i "$PP_PATH" > /tmp/scratch
使用xpath命令只提取entitlement信息到剪切板:
xpath /tmp/scratch '//*[text() = "Entitlements"]/following-sibling::dict' | pbcopy
现在剪切板包含了有效的entitlements,打开/tmp/entitlements.xml文件,删除
最终的/tmp/entitlements.xml文件内容包含像如下的entitlements信息:
keychain-access-groups
P9DNHPPVMB.*
get-task-allow
application-identifier
P9DNHPPVMB.com.jason.DeleteMe
com.apple.developer.team-identifier
P9DNHPPVMB
重点:/tmp/entitlements.xml这个文件的格式一定要正确,我在实践时因为xpath .... | pbcopy
命令生成了错误的
多了一个空格,而生成了不正确的entitlements.xml文件,导致安装成功后但启动app就崩溃
最后签名WordPress app
You now have performed all the setup.
You have a valid signing identity;
you have a valid provisioning profile embedded in the WordPress application at embedded.mobileprovision;
you have removed the Plugins directory;
and you have the entitlements of the new provisioning profile found at /tmp/entitlements.xml.
使用codesign命令用你的身份来重签名App:
codesign -f -s "iPhone Developer: Derek Selander (8AW8QLCX5U)" "$APP_FILE"/Frameworks/*
codesign --entitlements /tmp/entitlements.xml -f -s "iPhone Developer: Derek Selander (8AW8QLCX5U)" "$APP_FILE"
主要脚本代码
命令 | 文件 | 功能 | 命令行 |
---|---|---|---|
plutil | Info.plist | 替换CFBundleIdentifier | plutil -replace CFBundleIdentifier -string "xxx" Info.plist |
security | embedded.mobileprovision | 二进制解码为文本格式 | security cms -D -i xxx.mobileprovision |
codesign | 可执行文件 | 提取entitlements | codesign -d --entitlements :xxx.xml WeChat |
codesign | xxx.app | 重签名 | codesign --deep -f -s "iPhone Developer: [email protected] (TCDLVFEQHJ)" --entitlements zzz.xml xxx.app |
# 定义要重签名的app文件路径、和有效的mobileprovision文件路径
APP_FILE=~/Desktop/WeChat/Payload/WeChat.app
PP_PATH=~/Desktop/ProvisProfile_92618.mobileprovision
# 从mobileprovision文件中提取得到application-identifier(app id或CFBundleIdentifier)
EXTRACTED_ENT="/tmp/extracted_ent"
security cms -D -i "$PP_PATH" > "$EXTRACTED_ENT"
APP_IDENTIFIER=$(/usr/bin/xpath "$EXTRACTED_ENT" '//*[text() = "application-identifier"]/following-sibling::string[1]/text()' 2>/dev/null | cut -d. -f 2-80 )
echo "app id is : $APP_IDENTIFIER"
# 复制mobileprovision文件为app内的embedded.mobileprovision
cp "$PP_PATH" "$APP_FILE/embedded.mobileprovision"
# 删除PlugIns、Watch等文件夹
rm -rf "$APP_FILE/PlugIns"
rm -rf "$APP_FILE/Watch"
# 替换Info.plist内的CFBundleIdentifier、CFBundleDisplayName
plutil -replace CFBundleDisplayName -string "Woot" "$APP_FILE/Info.plist"
plutil -replace CFBundleIdentifier -string ${APP_IDENTIFIER} "$APP_FILE/Info.plist"
# 生成签名app用的entitlements.xml
codesign -d --entitlements :/tmp/entitlements.xml "$APP_FILE/WeChat"
security cms -D -i "$PP_PATH" > /tmp/scratch
# 这名命令有点缺陷:生成的 多了一个空格,正确的是
xpath /tmp/scratch '//*[text() = "Entitlements"]/following-sibling::dict' | pbcopy
# [不要用剪切板,否则会把上面命令得到剪切板中的内容给覆盖了。把剪切板上的内容粘贴到/tmp/entitlements.xml中]
open /tmp/entitlements.xml
# 重签名Frameworks、 和app
codesign --deep -f -s "iPhone Developer: [email protected] (TCDLVFEQHJ)" "$APP_FILE/Frameworks/"*
codesign --deep -f -s "iPhone Developer: [email protected] (TCDLVFEQHJ)" --entitlements /tmp/entitlements.xml "$APP_FILE"
# 安装到真机
mobdevim -i "$APP_FILE"
完整详细代码可以参考dsresign
参考
- dsresign App重签名的bash脚本
- iOS App Signer App重签名的应用程序
- Inside Code Signing
- 代码签名探析
- 《Advanced Apple Debugging & Reverse Engineering》Chapter 20: Code Signing