恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
iOS逆向实操:从原理到命令完成IPA重签名
首页
资讯中心
/
iOS逆向实操:从原理到命令完成IPA重签名
iOS逆向实操:从原理到命令完成IPA重签名
发布时间:2026/10/21 17:44:12
搞iOS逆向的朋友都知道砸壳只是第一步拿到手的是一个已经解密的主二进制文件但这时候的安装包还不能直接装到设备上。原因很简单原来的签名是别人家的你的设备不在人家的信任列表里。所以这个系列的第二篇就得认真聊聊IPA包重签名这件事。这篇不会讲太虚的东西我会从签名是干什么的讲起把证书、描述文件、entitlement、重签顺序、常见报错一条龙捋清楚最后给到可以直接抄作业的实操流程。这篇文章适合谁看刚接触iOS逆向手头有砸壳IPA包但装不上真机的开发者需要给自己的App做多版本测试但不想反复折腾Xcode签名的测试工程师还有想搞懂“为什么重签名必须按顺序来、为什么改了二进制就要失效”这些底层原理的人。看完之后你至少能做到拿到任意一个砸壳包十分钟内完成重签、安装到自己的设备上并且遇到报错知道去哪里查、怎么解决。1. 重签名前先把机制搞明白后面踩坑少一半1.1 代码签名到底在保护什么很多新手一上来就执行codesign命令换了个签名就跑结果装上去一打开就闪退然后开始怀疑工具链有问题。其实问题往往出在根本没理解iOS代码签名的设计逻辑。iOS的签名机制和Android的签名有一个本质区别Android签名只管“这个APK是不是某个开发者签的”用来做权限归属和应用更新校验而iOS的签名从iOS 5之后加入了大量完整性校验签名的对象不仅仅是“可执行文件头”而是整个应用Bundle里的几乎所有关键内容——主二进制、动态库、资源文件、甚至Info.plist里某些字段的hash都被记录下来写进了一个叫_CodeSignature/CodeDirectory的结构里。这就带来一个非常重要的推论你哪怕只往App里加了一个文件或者改了一个字节的二进制原本的签名就失效了。所以网上有人问“我能不能只重签主二进制不签Frameworks”结果安装到一半就报kAMDeviceInstallReturnCode -402653164这种错误。因为系统在验证主App签名时会同时校验它依赖的每个动态库的cdhash任何一个对不上整个安装都会被拒掉。我们常说的“重签名”其实做了三件事第一把原来开发者的身份标识擦掉换成你自己的证书身份第二重新计算整个 Bundle 内所有需要签名内容的哈希生成新的签名信息第三把描述文件里的授权信息entitlements也一并换掉让系统认为这个App是从正规渠道来的、信任的目标设备上安装的。理解了这个机制你再看后面那堆命令行就不会觉得是在“背命令”了。1.2 证书、描述文件、entitlement这三者是什么关系代码签名涉及三个容易混淆的概念我这里用通俗的方式解释一下。证书Certificate相当于你的“身份ID”。它是一段带有公钥和私钥配对关系的数字身份私钥保存在你电脑的钥匙串里公钥信息写入证书。系统通过证书判断“这个App是谁签的”。描述文件Provisioning Profile相当于一张“通行证”。里面包含了你的证书信息、允许运行的设备UDID列表开发证书情况下、App的Bundle ID列表、以及一堆沙盒权限能力entitlements描述。Entitlements授权相当于App的能力声明。比如要开推送、要开iCloud、要开Keychain共享都靠entitlements里面的键值。这个文件在签名时被单独放进去系统用来决定“你签的时候允许分配哪些权限”。三者关系你可以这样记证书证明“你是谁”描述文件规定“你能在哪些设备上跑哪个App”entitlements声明“这个App能用哪些系统能力”。重签名时三件事要一起换掉缺一不可。实操里最常见的错误就是用一个新的开发者证书去签但描述文件还是原来App内部的旧描述文件。这种情况安装时容易报错或者装上了打开就崩。正确做法是一定要用自己的描述文件替换掉App包里的embedded.mobileprovision否则系统读到描述文件里的证书ID和你签名用的证书不一致直接判定非法。另外还要区分“真机开发调试”和“个人免费证书”两种情况。如果你只是自己测试用Xcode里添加的免费Apple ID账号生成的开发证书也能签但描述文件里必须包含你的设备UDID而且Bundle ID不能和别人冲突最好在签名后顺手改一下Bundle ID免得和App Store版本冲突。企业证书或者个人99美元开发者账号就不一样了描述文件可以做成“任意设备”甚至“任意Bundle ID”自由度更高但这类证书本身有严格的使用条款我只建议在自己合理测试范围内用不要做违规分发的事。2. 重签名前的准备工具、材料、检查一个都不能少2.1 工具链选型命令行优先图形化作为补充做iOS重签名主流的工具链其实就那么几样我说下我的搭配习惯。首先是签名的核心工具codesign这是macOS自带的命令行工具归属在Xcode Command Line Tools里面。它负责给Mach-O文件、动态库、整个App Bundle做签名查看签名信息也靠它。这个工具最大的优点是稳定和Xcode的签名逻辑完全一致不会有兼容性问题。我强烈建议所有想做逆向的人先把它用熟再去碰那些一键重签工具。其次是**ldid**这个工具通常用来给Mach-O文件注入entitlements尤其在不需要完整签名或者临时修改权限时会用到。它是民间工具GitHub上能找到源码但它解决的问题在重签流程里并不必需——因为codesign --entitlements本身就能做到。我一般只用ldid在越狱调试场景下快速改权限常规重签用不上。然后是**iOS App Signer**这是一款图形化的重签名工具适合不习惯命令行的人它会把“选择证书、选择描述文件、调整Bundle ID、注入动态库”这些操作封装成表单界面。它的优点是快、直观但缺点是出了问题不好排查因为它把错误信息吞掉了一部分遇到复杂情况我还是得回到命令行。我的建议是图形化工具用来处理常规未修改包命令行用来处理要注入插件或者改动的包。最后还有一个容易被忽略的准备工作确认你的macOS上已经安装了Xcode或者至少安装了Command Line Tools。很多人从网上下载了codesign的别名脚本或者用旧版本工具链结果签名格式不兼容安装后系统直接提示应用损坏或签名无效。检查方法很简单终端里执行codesign --version能正常输出版本号就说明环境没问题。2.2 拿到IPA包之后先做静态检查这一步很多人会跳过去但我还是建议做。因为一旦你的IPA包本身就有问题比如主二进制和Mach-O架构不匹配、Info.plist的Bundle ID被改残了、动态库路径不对重签名流程再正确也白搭。先说说怎么找到一个可重签的IPA包。通常两条路要么自己用Xcode编译一个ipa要么从其他渠道获取已经砸过壳的包。砸壳这一步是这个系列第一篇的内容这里就不展开了只提醒一点未经砸壳的App Store包直接拿来重签是装不上的因为主二进制还是加密状态系统解密校验就会失败。拿到IPA之后第一步是与“解压”分不开的。IPA本质上就是个zip压缩包里面是Payload目录目录下的就是.app包。在终端里执行unzip YourApp.ipa -d extracted/ cd extracted/Payload/YourApp.app然后检查几个关键点看主二进制file YourApp确认是Mach-O 64-bit executable架构是arm64还是arm64e这决定你的目标设备支不支持。看Info.plistplutil -p Info.plist | grep -E CFBundleIdentifier|CFBundleExecutable|MinimumOSVersion确认Bundle ID、可执行文件名、最低系统版本。看签名信息codesign -dv YourApp 21 | grep -E Identifier|TeamIdentifier|Authority能看到当前签名身份。做完这三步检查你基本就能判断这个包“能不能重签”“重签要注意什么”。比如如果你发现主二进制是arm64e架构而你的测试机是iPhone X以前的A11芯片那架构就不支持签了也装不上运不了。这种问题在动手之前发现能省下大量调试时间。2.3 确认好你的签名身份和描述文件在命令行重签之前先确认电脑上可用的证书列表。打开终端security find-identity -v -p codesigning正常会输出若干行每行开头是一串SHA-1哈希后面跟着类似Apple Development: xxx (TEAMID)这样的描述。注意看括号里的Team ID是什么后面签名时要对应。如果你发现列表是空的说明钥匙串里没有任何可用于签名的证书得先去Xcode里添加一个Apple ID账号让它自动生成开发证书。描述文件的准备稍微繁琐一点。最常见的做法是用Xcode随便新建一个项目Bundle ID填你最终打算设置的ID注意最好不要和原始包冲突然后在Signing Capabilities里选择你的Team和证书让Xcode自动生成对应的embedded.mobileprovision。之后在终端里找到这个描述文件cd ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles/ ls -lt挑选最新生成的那个文件这就是属于你的描述文件。如果你不满意Xcode自动选择的授权项也可以用security cms -D -i embedded.mobileprovision导出描述文件内容手动编辑里面的entitlements字典再用security cms -S重新封装。这一步对初学的人来说比较绕实际操作时用Xcode自动生成的就够了除非你需要额外的权限比如App Group、Push等才需要手动补全。3. 核心实操从命令行到手把手完成IPA重签名3.1 最简单的场景不修改二进制直接重签如果你的目的是把别人签的包改成自己签名方便装到自己设备上调试而这个包本身不需要加任何插件或动态库那么算法非常简单先解压准备好描述文件和签名身份然后进入.app所在目录。第一步必须是先删除旧的签名和描述文件否则后面系统会认为你签了两层rm -rf YourApp.app/_CodeSignature rm -f YourApp.app/embedded.mobileprovision把你自己准备好的描述文件拷贝进去cp /path/to/your.mobileprovision YourApp.app/embedded.mobileprovision接下来重签动态库和插件。虽然这个最简单场景没有额外Frameworks但为了保险还是做一次“所有dylib统一签”的操作/usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ --entitlements /path/to/entitlements.plist \ YourApp.app/Frameworks/*.framework \ YourApp.app/PlugIns/*.appex 2/dev/null || true然后签主App/usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ --entitlements /path/to/entitlements.plist \ YourApp.app签完验证一下codesign -dv YourApp.app codesign --verify --verbose YourApp.app如果验证不通过会直接告诉你哪个文件哈希对不上。验证通过后回到外层目录重新打包cd extracted zip -r ReSigned.ipa Payload/最后用ios-deploy、Xcode、或者一些辅助工具安装到真机。这里有一个小技巧如果你用Xcode的Devices窗口装IPA有时候会卡在“正在安装”很久这时候可以直接用终端里的xcrun devicectl device install app --device UDID ReSigned.ipa通常更稳更省事。3.2 核心重点带着动态库注入重签的完整流程如果包里有额外注入的动态库比如你为了调试或研究加载的Tweak重签复杂度会明显上升。原因是iOS对动态库签名委托有严格的要求主签名一旦变化所有被依赖的动态库都要重新计算cdhash并写入主签名的CodeDirectory里。而且动态库本身的Mach-O结构里还藏着一堆签名相关的Load Command一旦被改过签名必须同步更新。注入方式有很多种比如用optool、insert_dylib往主二进制的Load Command里加一条LC_LOAD_DYLIB或者直接把动态库放到Frameworks目录然后修改依赖路径。无论哪种方式重签顺序都不能乱。我直接给出一个经过多次验证的完整命令序列假设你的.app包里有Frameworks/libHack.dylib目标是把它也签进去# 第一步删除旧签名 rm -rf YourApp.app/_CodeSignature rm -f YourApp.app/embedded.mobileprovision # 第二步放入你的描述文件 cp /path/to/your.mobileprovision YourApp.app/embedded.mobileprovision # 第三步先签所有动态库这一步非常关键 /usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ YourApp.app/Frameworks/libHack.dylib # 第四步如果还有其他framework也一并签掉 /usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ YourApp.app/Frameworks/*.framework # 第五步最后签主App /usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ --entitlements /path/to/entitlements.plist \ YourApp.app为什么要先签动态库而不是直接签主App因为codesign在处理主App签名时会自动遍历它依赖的所有子签名产物读取它们的cdhash。如果动态库还没签主App就找不到可以引用的hash要么报错要么签出来是残缺的。所以签名顺序永远是先签内层依赖再签外层主二进制。还有一个细节如果你的动态库是用insert_dylib注入的注入工具可能会修改主二进制里的Load Command这本身就会让原签名失效所以你必须在重签前完成注入然后一次性重签。注入后建议先otool -L YourApp.app/YourApp看一下依赖列表确认路径正确别等签完才发现Load Command里的路径是绝对路径或找不到文件。3.3 处理extension、watch等嵌套签名很多常见App里都带着扩展比如PlugIns目录下的.appex、Watch目录下的.app还有Frameworks里的WidgetKitExtension。iOS对这些组件的签名要求和主App不太一样扩展组件需要独立签名同时也要在父App的签名里被引用形成所谓“嵌套签名”。重签的时候如果没有对扩展单独签名就直接签主App会报类似code object is not signed at all的错误。解决办法很简单签主App之前先把PlugIns和Watch目录里所有.appex和.app都签一遍/usr/bin/codesign -f -s Apple Development: Your Name (TEAMID) \ YourApp.app/PlugIns/*.appex \ YourApp.app/Watch/*.app注意扩展的Bundle ID必须是主App Bundle ID的“子级”比如主App是com.example.app扩展就应该是com.example.app.widget。重签时如果改了主App的Bundle ID扩展的Bundle ID也必须同步改否则系统在安装时校验扩展的com.apple.developer.team-identifier和主App不一致直接拒绝安装。另一个常见问题是扩展里的Info.plist可能包含NSExtension相关键以及自己的CFBundleIdentifier如果原来的包是加密状态只改主App的Bundle ID是不够的必须把所有扩展、Watch App里的Bundle ID一起改掉。这一步手动改很累我一般写一个简单脚本批量改for plist in $(find YourApp.app -name Info.plist); do /usr/libexec/PlistBuddy -c Set :CFBundleIdentifier com.example.newid $plist 2/dev/null done四舍五入这就覆盖了大部分重签场景。签名顺序梳理下来其实就是改Bundle ID如需→ 删旧签名 → 放入新描述文件 → 签所有子组件 → 签主App → 重新打包 → 安装。3.4 用图形化工具快速重签什么场景下合适如果你要处理的包复杂度低没有动态库注入也不想记忆一长串命令行iOS App Signer这类工具很合适。基本操作就是选择.app包路径、选择证书、选择描述文件它会自动帮你完成重签并输出新的.ipa。但有几个场景我不建议用它第一包里带着注入的dylib且路径被改过图形工具处理不了动态库依赖顺序问题第二你需要修改entitlements而工具里没有对应的编辑入口第三你改了扩展的Bundle ID但工具不会同步修改子级扩展。说白了图形化工具适合“常规重签”不适合“手术型重签”。所以我的实际做法是准备一个“命令行签名脚本”遇到任何包都用脚本处理图形化工具偶尔用来对比验证确认自己命令行签出的包是否正常。两者搭配事半功倍。4. 实操中踩过的坑错误码、Entitlements冲突、重签后闪退4.1 安装失败的常见错误速查表重签最让人烦躁的其实是安装阶段的一堆错误码不同错误码对应不同原因我整理成一张速查表方便你对照排查。错误现象常见原因解决思路The application could not be installed描述文件里的设备UDID不在列表里用Xcode重新生成包含当前设备的描述文件或换企业级描述文件-402653164/kAMDeviceInstallReturnCode签名信息不完整通常动态库漏签或顺序错了先签所有Frameworks、PlugIns再签主AppApp installation failed: Application is invalidBundle ID重复或Info.plist被改坏重新修改Bundle ID检查CFBundleExecutable与主二进制是否匹配installation failed: no codesign代码签名缺失确认codesign -dv能正常输出检查证书身份拼写是否带TeamID安装成功但一打开就闪退Entitlements里的权限和描述文件不一致或动态库找不到检查embedded.mobileprovision里的entitlements用codesign -d --entitlements - YourApp对比桌面端报“应用损坏无法打开”下载来源和签名信任策略导致在Finder中右键→打开或检查签名是否已验证这表看着简单但每一条背后我都踩过。印象最深的一次是给某个重签包安装时报-402653164查了半天才发现是某个Frameworks目录下多了一个旧表述的签名残留codesign默认会用旧签名去校验后来把整个_CodeSignature目录删掉重来才行。4.2 Entitlements冲突闪退的一大隐藏元凶很多人重签完能装上但一打开就秒退翻遍系统日志也只看到一条abort()信息根本不知道发生了什么。这种情况最常见的根源就是Entitlements不匹配。iOS安装App时会做两件事第一把描述文件里的entitlements写入系统数据库第二用签名里的entitlements去校验App能不能获得到它请求的权限。如果你签名时使用的entitlements比描述文件允许的更多比如描述文件里没开启keychain-access-groups但你签名时硬塞进去了系统会认为这个App越权了直接拒之门外——表现就是启动即崩。正确做法是重签前先把原App内的entitlements导出然后用编辑工具保留合理的权限项再删除掉你的开发环境不支持的那些比如application-identifier、aps-environment这类可能冲突的键最后签入。导出命令codesign -d --entitlements - YourApp.app 2/dev/null另一个注意点是com.apple.security.application-groups如果你重签后还需要App Group共享数据必须在描述文件里也声明同样的Group ID否则即使签名里有这个entitlements系统也不会给你数据容器。4.3 动态库重签的坑嵌套签名和路径依赖动态库重签和普通App重签有个不同点动态库通常要维护一个LC_ID_DYLIB路径它相当于动态库自己对外宣称的名字。如果你注入时用的install_name是绝对路径到了其他设备上就找不到了主App启动时会不断尝试加载这个路径找不到就报错。遇到这种情况可以用install_name_tool修改动态库的IDinstall_name_tool -id executable_path/Frameworks/libHack.dylib \ YourApp.app/Frameworks/libHack.dylib改完之后同样要先签动态库再签主App顺序不能变。另外如果你注入的动态库还依赖其他动态库比如依赖系统库libsqlite3.tbd在目标设备上系统库路径一般是固定的不会踩坑但如果你把第三方库也放在Frameworks里它的相对路径也要一并处理。4.4 真机调试线上问题的排查思路有时候重签完手机上能装能启动但行为不对这时候需要看系统日志。没越狱的环境下可以用Xcode的Devices窗口查看Console日志或者用xcrun simctl spawn booted log stream看模拟器日志。真机的话Xcode 15以上版本自带Device Activity也能实时看日志。我遇到过一种情况App在测试机上能启动但功能异常报错信息显示某个文件_CodeSignature校验失败。最后定位到是资源文件被改动了但没有重签。iOS的签名校验范围不光覆盖二进制还会覆盖部分资源文件。如果资源文件被zip解压时破坏了权限信息比如可执行权限丢了主App仍然能启动但动态库加载就会失败。所以重签前最好先chmod -R 755确保文件权限正常。5. 一点更高效的工作流建议5.1 把重签做成一条命令如果你打算长期做逆向分析手动敲命令会非常浪费生命。我的做法是写了一个脚本把“解压→删除旧签名→放描述文件→签子组件→签主App→打包”全流程串起来核心参数用环境变量传入。这样一来拿到任何IPA包一行命令就完成重签。脚本逻辑参考如下bash思路#!/bin/bash IPA_PATH$1 SIGN_IDApple Development: Your Name (TEAMID) MOBILEPROVISION/path/to/your.mobileprovision unzip -q $IPA_PATH -d tmp_signed APP_PATH$(find tmp_signed/Payload -name *.app -maxdepth 1) rm -rf $APP_PATH/_CodeSignature rm -f $APP_PATH/embedded.mobileprovision cp $MOBILEPROVISION $APP_PATH/embedded.mobileprovision find $APP_PATH -name *.framework -exec codesign -f -s $SIGN_ID {} \; find $APP_PATH -name *.appex -exec codesign -f -s $SIGN_ID {} \; codesign -f -s $SIGN_ID --entitlements /path/to/entitlements.plist $APP_PATH codesign --verify --verbose $APP_PATH || echo Verify failed cd tmp_signed zip -qr ReSigned.ipa Payload/这段脚本覆盖了90%的日常重签场景。如果你多了动态库注入就在签主App之前加一行签dylib的命令就行。5.2 重签后的验证流程重签不是“签完就完事”我强烈建议装到设备上之后再做一轮完整验证。第一轮是静态验证codesign --verify、spctl --assess看能否通过第二轮是动态验证安装后运行用Xcode的Debug菜单附加进程观察前几秒日志有没有签名相关报错第三轮是行为验证把关键功能都点一遍特别是涉及沙盒、推送、网络的部分确认entitlements没有影响。有一次我重签一个音乐类App装上去也能放歌但后台播放失效。后来排查发现原App的entitlements里带了audio的Background Mode而我重新生成的描述文件没有申请这个Background Mode权限所以系统不让它后台运行。这个教训说明保留原App合理权限非常重要不要为了“能装”就砍掉权限否则功能会隐性缺失。5.3 学习路径与资料整理如果你刚入门我的建议是多做几个不同类型的包别只盯着一个。可以先拿自己编译的简单App练手再尝试一个带Watch Extension或Widget的包最后再挑战带动态库注入的复杂包。每类包遇到报错都记下来整理成自己的答案库。理解底层原理会更进一步去看一下苹果官方关于Code Signing的文档再结合Mach-O结构相关的资料搞清楚__LINKEDIT段里那些LC_CODE_SIGNATURE到底怎么组织。虽然日常工作用不到这么深但一旦遇到诡异问题懂底层会让你直接锁定方向而不是瞎猜。做了这么久逆向和签名相关的活儿我个人最大的体会是重签名本身不难难的是对“签名体系”的整体理解。命令谁都能敲但出了问题能不能在三分钟内定位考的是对证书、描述文件、Mach-O结构、entitlements这套东西串联起来的熟悉程度。把这篇文章里的逻辑吃透了再配一份顺手好用的签名脚本基本上市面上90%的IPA重签需求你都能拿捏住。剩下那10%多是特殊架构、特殊权限和奇葩描述文件组合出来的问题到时候你再回来看这篇思路会清晰得不是一点半点。