rea-reverse

iOS 描述文件与 IPA 查看器

拖入 .mobileprovision、.provisionprofile、.ipa、压缩的 .app 或 .xcarchive、Info.plist 或 .entitlements 文件,就能看清里面写了什么:描述文件类型、过期时间、团队、设备和证书;App 的 Bundle ID、隐私权限说明和内嵌框架;以及可执行文件实际签进去的 entitlements,并逐项和描述文件对比。

描述文件和安装包不会离开你的浏览器. 文件在本地一个没有网络权限的 Web Worker 里读取。IPA 通过 ZIP 目录按需读取:只打开各个 plist、内嵌描述文件,以及可执行文件的头部和签名。 如何自己验证

在单独页面打开工具(无广告、无统计)

iOS 测试包装不上的时候,报错很少直接告诉你签名的哪一环出了问题。“无法安装”“The executable was signed with invalid entitlements”(0xe8008016)“A valid provisioning profile for this executable was not found”(0xe8008015),归根到底都落在两处的几项信息上:描述文件,以及可执行文件的代码签名。在 Mac 上一般这样看:

security cms -D -i embedded.mobileprovision
codesign -d --entitlements :- Payload/MyApp.app

这个页面直接从文件里读出同样的数据,在浏览器里完成,并把两边并排对照。它面向的是检查自己的包和描述文件:测试机为什么装不上、导出的 IPA 里到底塞的是哪个描述文件、推送 entitlement 写的是 development 还是 production。

使用步骤

  1. 准备要检查的包或描述文件:.ipa、压缩过的 .app 或 .xcarchive,或者 .mobileprovision 文件(Xcode 下载的描述文件在 ~/Library/MobileDevice/Provisioning Profiles/ 里)。
  2. 把它拖到上方的拖放区,或者点“选择文件”。文件在浏览器里读取,不会上传。
  3. 先看“概览”里的检查结果(有效期、Bundle ID、entitlements、团队、签名证书),再看描述文件的类型和设备列表,以及“代码签名里的 entitlements”表:每一项签入的值旁边就是描述文件允许的值。

真实输出示例

我们把本站测试用例里的 IPA 样本拖进本页,把页面显示的结果复制下来。排版做了压缩(标签和值放在同一行,省去应用信息和 Info.plist 部分),所有值都原样保留。剩余天数按抓取当天(2026-11-02)计算。

样本: 本站代码仓库里的 test/ios/good.ipa:由 test/ios/build.sh 用一次性的 OpenSSL CA 生成的合成 IPA。团队 FIXTEAM123、各个 UDID 和全部证书都是虚构的,不涉及任何真实的 Apple 描述文件。

概览
文件类型  IPA
文件大小  20.8 KB
正常 描述文件有效期到 2036-01-01(还剩 3346 天)。
正常 Bundle ID com.example.fixture 与描述文件的 App ID FIXTEAM123.com.example.fixture 匹配。
正常 可执行文件签入的每一项 entitlement 都在描述文件允许的范围内。
正常 团队 FIXTEAM123 与描述文件的团队一致。
正常 签名证书(Apple Development: Fixture Developer (FIXDEV0001))在描述文件的开发者证书列表里。
正常 每个 App 扩展的 Bundle ID 都以 com.example.fixture 开头。

描述文件
名称            Fixture Dev Profile
描述文件类型    开发(DEVELOPMENT) 列有设备,且允许 get-task-allow(可挂调试器)
App ID(application-identifier)  FIXTEAM123.com.example.fixture
UUID            F7021ABE-0000-4000-8000-000000000001
团队            FIXTEAM123 Fixture Org
平台            iOS
过期时间        2036-01-01 00:00:00 UTC 还剩 3346 天
已注册设备(3)
  00008110-000A1B2C3D4E5F01
  00008120-000A1B2C3D4E5F02
  0123456789abcdef0123456789abcdef01234567
开发者证书(1)
  Apple Development: Fixture Developer (FIXDEV0001)  FIXTEAM123  AB:65:6E:F8:2E:D5:84:B2:7A:D2:03:4F:5E:39:27:35:75:82:9C:CD
…

主可执行文件与代码签名
架构    平台  最低系统版本    LC_ENCRYPTION_INFO
arm64   iOS   15.0, SDK 27.0  cryptid 0
arm64e  iOS   15.0, SDK 27.0  cryptid 0
…
代码签名里的 entitlements
键                                      签入的值                        描述文件允许                    状态
application-identifier                  FIXTEAM123.com.example.fixture  FIXTEAM123.com.example.fixture  允许
aps-environment                         development                     development                     允许
com.apple.developer.associated-domains  applinks:example.test           *                               允许
com.apple.developer.team-identifier     FIXTEAM123                      FIXTEAM123                      允许
get-task-allow                          true                            true                            允许
keychain-access-groups                  FIXTEAM123.com.example.fixture  FIXTEAM123.* com.apple.token    允许

可以拖进来的文件

  • 描述文件:.mobileprovision(iOS、iPadOS、tvOS、watchOS、visionOS)和 macOS 的 .provisionprofile。描述文件是一个 CMS SignedData,里面包着 XML plist;现在的描述文件用定长编码,早期的描述文件和 openssl cms -stream 生成的是 BER 不定长编码,两种都能读。security cms -D 输出的 XML 也可以直接拖进来。
  • 安装包:.ipa、压缩过的 .app(访达里的“压缩”)或压缩过的 .xcarchive。先只读 ZIP 中央目录,然后只打开 App 的 Info.plist、embedded.mobileprovision、PrivacyInfo.xcprivacy、各扩展的 Info.plist 和主可执行文件。几个 GB 的 IPA 也不会整个读进内存,支持 ZIP64。
  • 单个文件:XML 或二进制(bplist00)格式的 Info.plist、.entitlements、PrivacyInfo.xcprivacy,或者一个单独的 Mach-O 可执行文件(单架构或通用二进制),用来列出签名里的 entitlements。

能看到什么

描述文件:名称、App ID 名称、App ID(application-identifier,带团队前缀)、UUID、团队 ID 和名称、平台、创建和过期时间(以及还剩几天)、是否由 Xcode 管理、完整的 Entitlements 字典、注册设备的 UDID 列表(带搜索框)、开发者证书(通用名、取自 OU 字段的团队、SHA-1、有效期),以及 CMS 签名者的证书。

描述文件类型并不写在文件里,这里按 Xcode 和 fastlane 的判断方式推出来:

描述文件包含 类型
ProvisionsAllDevices = true 企业(In-House);macOS 上是 Developer ID
ProvisionedDevices,且 get-task-allow = true 开发(Development)
ProvisionedDevices,get-task-allow = false Ad Hoc
两者都没有 App Store(macOS 上是 Mac App Store)

App:Bundle ID、版本和 build 号、最低系统版本、SDK 和 Xcode 版本、设备类型、支持的平台、URL Scheme、必需的设备能力、App Transport Security 配置、每一条 NS…UsageDescription 及其文案、隐私清单(追踪开关、追踪域名、收集的数据类型,以及每个需要理由的 API 类别和理由代码,例如 CA92.1)、内嵌的框架和 dylib(标出自带隐私清单的那些),以及 App 扩展的 Bundle ID 和扩展点。

可执行文件:各架构切片(arm64、arm64e 等)及其 LC_BUILD_VERSION 里的平台和最低系统版本、LC_ENCRYPTION_INFO_64 的 cryptid(为 1 表示 App Store 下载的包,代码是加密的)、CodeDirectory 里的签名标识符和团队、是否为 ad hoc 签名、签名证书,以及代码签名里两份 entitlements:XML plist(slot 5)和 iOS 15 起实际校验的 DER 版本(slot 7)。两份不一致时页面会提示。

会做哪些检查

拖入的是带描述文件的包时,页面会把常见安装失败背后的几项逐一核对:

  • 过期:描述文件已过期,或 30 天内过期。
  • Bundle ID:CFBundleIdentifier 必须匹配描述文件 App ID 去掉团队前缀后的部分,* 是通配符(TEAMID.com.example.*)。
  • Entitlements:可执行文件签入的每一项 entitlement 都必须在描述文件允许的范围内。字符串和数组可以用描述文件里的通配符(钥匙串分组的 TEAMID.*、Associated Domains 的 *);App Store 或 Ad Hoc 包里 get-task-allow 为 true、production 描述文件配上 development 的 aps-environment、别的团队的钥匙串分组、描述文件没有开通的能力(iCloud、App Groups),都会列出键名和两边的值。macOS 上 com.apple.security. 开头的沙盒和 Hardened Runtime 权限不需要描述文件,会单独标出。
  • 团队:签入的 application-identifier(或 CodeDirectory)里的团队与描述文件的团队是否一致。
  • 签名证书:代码签名里的证书必须在描述文件的 DeveloperCertificates 里,按 SHA-1 比对。对应的就是“描述文件不包含签名证书”这种情况。
  • 扩展:每个 .appex 的 Bundle ID 都必须以主 App 的 Bundle ID 加一个点开头。

想确认某台设备在不在描述文件里,把它的 UDID 粘贴到设备列表上方的输入框。比对只在页面里进行,不会发送到任何地方。

我们是怎么验证的

测试样本都是合成的,由 test/ios/build.sh 在 macOS 27.0.1、Xcode 27.0(iPhoneOS 27.0 SDK)和 OpenSSL 4.0.3 上生成:一个一次性的 CA、一张“开发者”证书和一张描述文件签名证书;五个手写的描述文件(开发、BER 不定长编码的 Ad Hoc、已过期的 App Store、App ID 为 * 的企业版,以及一个 macOS 开发描述文件),用 openssl cms -sign -binary -nodetach 签名;再把一个很小的 C 程序编译成 arm64 和 arm64e,用 codesign --entitlements --generate-entitlement-der 签名后打包成 Payload/Fixture.app。另一个“错误”的包故意做错了一堆东西:Bundle ID 不对、对着 App Store 描述文件签了 get-task-allow true 和开发环境推送、用了别的团队的钥匙串分组、多了一个描述文件没有的 iCloud 权限、签名证书不在描述文件里、Widget 扩展的 Bundle ID 前缀不对,还有一个切片的 cryptid 被改成了 1。单元测试把解析结果和 security cms -D、plutil、codesign -d --entitlements(XML 和 --der 两种)、lipo -archs、otool -l 的输出逐项比对。我们还用解析器跑了一遍这台 Mac 上 Xcode 下载的 20 个真实描述文件:名称、UUID、团队、过期时间、设备列表、entitlements 和证书 SHA-1 全部 20 个都与 security cms -D 一致(这些数据不在本站和测试里出现)。

做样本时踩了两个坑。plutil -convert json 会拒绝解码后的描述文件,报 “Invalid object in plist for JSON format”,因为描述文件里有 JSON 表示不了的 <date> 和 <data>,所以构建脚本改用 plutil -p 生成参照输出。另外 codesign -dv 根本不打印 Authority= 行,要用 -dvvv 才能看到是哪张证书签的。如果把 security cms -D -i 直接用在 .ipa 上而不是里面的描述文件,会报 security: failed to add data to decoder: UNKNOWN (-8183(d):先解压,或者直接把 IPA 拖到这里。

你的文件如何保持私密

  • 查看器运行在一个单独的框架(iframe)里,来源是 tools.rea-reverse.com;文件在框架里的 Web Worker 中、在你的设备上读取,不上传,这个工具也没有服务端。
  • UDID、团队 ID、Bundle ID 和证书信息只显示在框架里。粘贴的 UDID 只在框架里比对,不保存。
  • 这个页面在正文下方展示一个广告。广告脚本运行在 rea-reverse.com 上,而框架属于另一个源(origin),浏览器不允许广告脚本或本页的任何其他脚本读取你拖入的文件、粘贴的 UDID 或读取结果。
  • 框架的内容安全策略拦截它的脚本能发起的所有请求(connect-src 'none',也不加载任何第三方资源)。框架也不能自行打开窗口或让本页跳转。它传给本页的只有自身高度和下面这些统计事件。
  • 统计事件只含粗粒度的值:成功或错误码、文件类型(profile、ipa 等)、推出的描述文件类型、未通过的检查项数量区间,以及文件大小和耗时区间。框架把事件交给本页,由本页发送。完整列表见隐私政策。
  • 不想要任何广告和统计?在单独页面打开查看器。

如何自己验证

  1. 打开浏览器开发者工具的 Network(网络) 面板,加载本页。发往 tools.rea-reverse.com 的请求就是框架:它的 HTML、样式、字体和脚本,以及 Worker 脚本 /_astro/ios-worker-*.js。发往 Google 域名的请求是本页的广告。
  2. 清空日志,然后拖入一个 IPA。在过滤框里输入 domain:tools.rea-reverse.com:最多只会再出现一次 Worker 脚本 /_astro/ios-worker-*.js(为下一次运行准备的新 Worker),没有任何请求带着你的数据。广告请求可能还会继续,但它们来自本页,不带框架里的任何内容。
  3. 点开来自 tools.rea-reverse.com 的 ios-provisioning-profile-viewer/ 文档,查看 Headers:它的 Content-Security-Policy 里有 connect-src 'none' 和 frame-ancestors https://rea-reverse.com。在 Chrome 里,Application(应用)> Frames 也能看到框架的源和策略。Worker 运行在 default-src 'none'; script-src 'self'; connect-src 'none' 这条策略下,浏览器会拦下它发出的任何请求。
  4. 检查隔离:在 Console(控制台) 里,上下文选 top,运行 document.getElementById('tool-frame').contentDocument,结果是 null;frames[0].document 会抛出 SecurityError。广告脚本拿到的也是这个结果。
  5. 或者先打开页面再断网,然后拖入一个描述文件,照样能用。
  6. 用你自己的工具对照:security cms -D -i file.mobileprovision、codesign -d --entitlements :- MyApp.app 和 otool -l MyApp | grep -A4 LC_ENCRYPTION 会给出同样的值。

局限

  • 不做任何密码学校验。 既不校验 Apple 对描述文件的签名,也不校验 App 的代码签名;签名后被改过的描述文件或二进制在这里看着一切正常,但设备会拒绝。
  • 不解密、不重签、不修改。App Store 下载的包只读取 load command 和签名。
  • App 扩展只列出 Bundle ID,不交叉检查它们自己的描述文件和 entitlements。需要的话把扩展的 embedded.mobileprovision 单独拖进来。
  • Entitlements 的比对规则覆盖常见情况(精确值、* 通配符、数组取子集)。Apple 安装器还有一些特殊规则这里没有模拟,例如只授予 Apple 自己签名代码的权限。
  • 设备列表来自描述文件本身。设备在开发者账号里是否仍处于启用状态,从文件里看不出来。

对应的命令行

在 Mac 上先解压 IPA(直接对 IPA 运行 security cms -D 会失败),再解码里面的描述文件,用 plutil 取字段(macOS 27.0.1):

$ unzip -q good.ipa && security cms -D -i Payload/Fixture.app/embedded.mobileprovision | plutil -extract ExpirationDate raw -o - -
2036-01-01T00:00:00Z
$ security cms -D -i Payload/Fixture.app/embedded.mobileprovision | plutil -extract ProvisionedDevices json -o - -
["00008110-000A1B2C3D4E5F01","00008120-000A1B2C3D4E5F02","0123456789abcdef0123456789abcdef01234567"]

可执行文件实际签入的 entitlements(描述文件必须允许这些值):

$ codesign -d --entitlements - --xml Payload/Fixture.app
Executable=…/Payload/Fixture.app/Fixture
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>application-identifier</key><string>FIXTEAM123.com.example.fixture</string><key>aps-environment</key><string>development</string><key>com.apple.developer.associated-domains</key><array><string>applinks:example.test</string></array><key>com.apple.developer.team-identifier</key><string>FIXTEAM123</string><key>get-task-allow</key><true/><key>keychain-access-groups</key><array><string>FIXTEAM123.com.example.fixture</string></array></dict></plist>

没有 Mac 时,OpenSSL 打印的是同一份 plist(OpenSSL 4.0.3;-noverify 跳过证书链校验)。它的输出和 security cms -D 逐字节相同:

$ openssl cms -verify -noverify -inform DER -in Payload/Fixture.app/embedded.mobileprovision
CMS Verification successful
<?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>AppIDName</key>
        <string>Fixture &amp; Friends</string>
…
        <key>ExpirationDate</key>
        <date>2036-01-01T00:00:00Z</date>
        <key>Name</key>
        <string>Fixture Dev Profile</string>
…

常见问题

怎么解码 .mobileprovision 文件?

拖到这里,就能看到逐项排好的字段,描述文件类型、剩余天数和证书 SHA-1 也都算好了。在 Mac 上,security cms -D -i file.mobileprovision 打印里面的 plist;在其他系统上,openssl cms -verify -noverify -inform DER -in file.mobileprovision 效果相同。

没有 Mac,能在线查看描述文件吗?

能。本页在 Windows、Linux、macOS 上的现代浏览器里都能用,描述文件在你设备上的 Web Worker 里读取,所以描述文件、其中的 UDID 和证书都不会上传。

“A valid provisioning profile for this executable was not found”是什么意思?

设备上没有找到与这个包匹配的已安装描述文件。原因通常就是本页检查的几项之一:描述文件过期、设备 UDID 不在其中、Bundle ID 与它的 App ID 不匹配,或者签名证书不在它的开发者证书列表里。

我的 IPA 里用的是哪个描述文件?

就是 Payload/<App>.app/embedded.mobileprovision,页面在“描述文件”一节显示它的名称和 UUID。Xcode 导出时选中的描述文件可能和你以为的不一样,自动签名时尤其如此;看 UUID 就知道到底是哪一个。

描述文件里明明有这个 entitlement,还是装不上。

看“签入的值”那一列:不光键要有,值也必须被允许。用发布描述文件签名的包里 aps-environment 却是 development,是最常见的情况;钥匙串分组的团队前缀写错也很常见。再看签名证书那一行:证书续期后重新生成的描述文件里,已经没有旧证书了。

怎么查设备的 UDID?

连上设备,在访达里点设备名称下方那行文字,直到出现 UDID;或者在 Xcode 的 Window > Devices and Simulators 里查看。把它粘贴到设备列表下方的输入框里,就能和描述文件比对。

更新于

相关工具和教程