为什么我们自己开发的App
安装包不能随便安装到任意的手机呢?App
安装包是自己的、手机是自己的,结果就是安装失败,有没有想过这个问题?下面我们来讲讲苹果公司设计的对App
安装包的签名
机制,并重点的讲解下怎么进行重签名
以及反重签名
的做法。
一、代码签名原理
想要重签名我们的APP安装包
,我们先来了解下APP原始包
的签名得到APP安装包
过程。先上整体的流程图,然后再来解释每一步操作的过程:
①:通过Mac电脑
创建的CSR文件
向苹果服务器
申请证书
,CSR文件
其实本质是Mac电脑
创建的一对RSA公私钥
中的公钥M
,我们把CSR文件
传给苹果服务器
,苹果服务器
使用私钥A
对CSR文件
进行加密和hash签名处理,生成一个证书
文件。
②:我们从苹果服务器
下载证书
和描述文件
并安装到当前的Mac电脑
中,Mac电脑
会将对应的私钥M
也证书
绑定存放在一起。(在手动管理证书的年代,为什么不能从苹果服务器
直接下载了证书
使用,而是一定要从证书创建者的Mac电脑
中导出证书呢?就是因为私钥M
的存在,现在是不是就能理解了)
③:打包的过程中,Mac电脑
会使用证书下的私钥M
对我们的原始APP包
进行签名处理;并把证书
以及描述文件
都打包到APP的安装包
中。
④:当我们的设备安装APP
时,会先通过设备内嵌的公钥A
对证书
、描述文件
做解密等处理,获取其中的内容,然后验证证书
中的HASH
值,来判断证书
是否合法;验证APP
的签名数据,判断APP
是否被篡改过;判断当前设备
是否存在可安装的设备列表
中,判断描述文件
与info.plist
中的BundleID
是否一致等等。
⑤:最后完成APP
的安装
Tips:提供一些查看或查找
CSR文件
、证书
或描述文件
内容使用到的命令
//查看CSR文件中的公钥内容
$cat CertificateSigningRequest.certSigningRequest
//查看CSR文件的其他信息(邮箱、加密方式、hash值算法)
$openssl asn1parse -i -in CertificateSigningRequest.certSigningRequest
//查看本机所有证书
$security find-identity -v -p codesigning
//查看描述文件的内容
$security cms -D -i 描述文件路径
二、通过终端命令手动重签名
我们在重签名之前,需要提前准备一些重签名必要的东西。
- 砸过壳的
IPA
包 :可以去PP助手中下载越狱应用,自己砸壳的文章之后再进行分享;- 可正常使用证书 :重签名
IPA
包,意思是指替换掉旧的签名证书,使其能正常安装;
有了以上准备,我们就具体来试试手动重签名的操作。
第1步:解压砸过壳的IPA
包,删除部分无法重签名的文件
A:删除`Payload` → `XXX.app` → `PlugIns`文件夹
B:删除`Payload` → `XXX.app` → `Watch`文件夹
第2步:对Payload
→ XXX.app
→ Framework
文件夹下的XXX.framework
进行签名。注意:如果IPA包中没有Framework文件夹,则可以跳过这步
//进入`XXX.app`目录下,执行如下命令(有很多`.framework`时需要多次执行)
$codesign -fs "证书" 需要签名的文件
第3步:给App
的可执行文件读写权限。
//进入`XXX.app`目录下,执行如下命令
$chmod +x 可执行文件名称
第4步:拷贝embedded.mobileprovision
文件到Payload
中,修改info.plist
中的Bundle identifier
值
a. 将新证书的`embedded.mobileprovision`文件拷贝到`Payload`中
b. 将`info.plist`中的`Bundle identifier`值改为新证书对应的`Bundle identifier`值
第5步:生成.plist
的权限文件
a. 进入`XXX.app`目录下,使用命令查看描述文件:$security cms -D -i 描述文件路径
b. 拷贝`Entitlements`键下的字典内容,将字典内容存储在新建的`XX.plist`文件
c. 把新建的`XX.plist`文件拷贝到`Playload`文件夹中
第6步:签名整个APP
包
//进入`Payload`文件夹下,执行如下命令
$codesign -fs "证书名称" --no-strict --entitlements=XX.plist XXX.app
//查看APP的签名信息
$codesign -vv -d APP路径
//查看可执行文件的加密信息
$otool -l 可执行文件名称 | grep crypt
第7步:将已签名的APP
包打包成IPA
文件
//进入`Payload`的上级文件夹下,执行如下命令
$zip -ry XXX.ipa Payload
注意:手动重签名会出现很多安装异常的问题,因为可能有很多小细节没有处理或出现问题,所以一般都使用Xcode
进行重签名处理。
三、通过Xcode进行重签名
苹果签名的所有细节处理都已经封装在Xcode
中,所以我们可以使用Xcode
来替我们做签名处理,只要替换Xcode
签名的目标原文件
达到欺骗Xcode
的目的,使其对我们想要的原文件
进行签名处理。
1、新建一个名为`AAA`的空工程,编译、运行使其安装到真机中。
2、解压砸过壳的`IPA`包,拷贝`Payload` → `XXX.app`到工程的`Products`下,重命名并替换`AAA.app`。
3、给`App`的可执行文件读写权限,进入`AAA.app`目录下执行命令:`$chmod +x 可执行文件名称`
4、删除部分无法重签名的文件;`①、删除Payload→AAA.app→PlugIns文件夹;②、删除Payload→AAA.app→Watch文件夹`
5、对`Payload`→`AAA.app`→`Framework`文件夹下的`XXX.framework`进行签名,进入`AAA.app`目录下执行命令:`$codesign -fs "证书" 需要签名的文件`
6、修改`AAA.app`→`info.plist`中的`Bundle identifier`值与当前工程的`Bundle identifier`值一致。
7、使用快捷键`command+R`运行当前的工程,此时Xcode已经完成了重签名处理。
最后注意:如果`IPA`包中没有`Framework`文件夹,则直接跳过第5步。
相比第一种手动重签名的方式,Xcode
重签名就相对简单多了!
四、通过Run Script脚本进行重签名
相对手动重签名,Xcode
重签名已经简单很多了,但是还不是最简单的
第1步:新建一个空工程,在工程目录下新建APP
文件夹,将IPA包拷贝到APP
目录下。
第2步:选择空工程Build Phases
→ +
→ New Run Script Phase
添加一个脚本的入口
第3步:将如下的脚本内容,拷贝到Run Script
中
# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH="${SRCROOT}/Temp"
#资源文件夹
ASSETS_PATH="${SRCROOT}/APP"
#ipa包路径
TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"
#新建Temp文件夹
rm -rf "${SRCROOT}/Temp"
mkdir -p "${SRCROOT}/Temp"
#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")
# echo "路径是:$TEMP_APP_PATH"
#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"
echo "app路径:$TARGET_APP_PATH"
rm -rf "$TARGET_APP_PATH"
mkdir -p "$TARGET_APP_PATH"
cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"
#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf "$TARGET_APP_PATH/PlugIns"
rm -rf "$TARGET_APP_PATH/Watch"
#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
# 设置:"Set : KEY Value" "目标文件路径"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"
#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`
#上可执行权限
chmod +x "$TARGET_APP_PATH/$APP_BINARY"
#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"
if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];
then
for FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*
do
#签名
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"
done
fi
五、重签名APP的用处
描述了这么多的重签名方法,有什么用处呢?当然不只是简单的在一个设备上安装多个相同应用这么简单。比如苹果商店有很多需要付费下载的应用,通过重签名后,就能免费进行安装和使用了;类似国内中的同步推
、91助手
等平台提供很多免费应用,而这些应用在App Store
中可能就需要付费下载了。所以大概总结了以下几点重签名的用途:
1、破解需付费下载的应用,比如:同步推
、91助手
等平台。
2、通过注入Framework
来Hook
重签名APP
中的方法,修改代码的执行顺序,比如:制作微信抢红包的外挂。
3、动态调试重签名的APP
,查看界面布局等,比如:探究竞争对象发布的APP
的新功能。
六、防止重签名的处理
在逆向编程中,重签名是一个很常用的的动态调试基本操作,所以做重签名的防护是很必要的一个步骤,下面来讲下防止别人重签名你的APP
需要怎么处理。
我们先查看下Xcode
使用的证书
和APP
中描述文件
的对应关系:
第1步:进入Mac电脑
中的钥匙串
中,选择证书,双击签名使用的证书
,查看并拷贝组织单位
的编号
。
第2步:进入XXX.app
路径下,使用命令security cms -D -i embedded.mobileprovision
查看embedded.mobileprovision
内容,找到key = application-identifier
对应的value
值。
结合文章开篇所述的重签名
步骤来思考,在任何必要的时候(例如:APP
启动等),是否可以通过检测APP
签名证书
中的组织单位ID
是否与Xcdoe
工程中的内容一致来判断当前APP
是否已经被重签名过。
void checkAppCodesignReplaced(NSString *bundleId)
{
//描述文件路径
NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
//读取application-identifier注意描述文件的编码要使用:NSASCIIStringEncoding
NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
NSArray *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (int i = 0; i < embeddedProvisioningLines.count; i ++) {
if ([embeddedProvisioningLines[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
NSInteger fromPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"<string>"].location+8;
NSInteger toPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"</string>"].location;
NSRange range = NSMakeRange(fromPosition, (toPosition - fromPosition));
NSString *fullIdentifier = [embeddedProvisioningLines[i+1] substringWithRange:range];
NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
NSString *appIdentifier = [identifierComponents firstObject];
//对比签名ID
if (![appIdentifier isEqual:bundleId]) {
asm( //exit
"mov X0, #0\n"
"mov w16, #1\n"
"svc #0x80"
);
}
break;
}
}
}
注意:使用内联汇编代码(asm)
是防止逆向工程师
通过符号断点
来定位exit
的调用位置。
七、后续
在APP中仅仅加入防止重签名
是远远不够的,对于逆向开发工程师
来说,这种操作很容易就破解了。比如说使用HOOK
,替换判断组织单位
的编号
是否一致的方法,亦或是修改汇编代码,使用b指令
直接跳过验证方法
等等。所以我们还需要做很多其他的处理,才能达到APP安全防护
的目的,比如说:反HOOK防护
、ptrace防护
、混淆关键代码
、隐藏敏感方法调用
等。此篇文章记录到此,其他的安全防护处理,在之后的文章另做的技术记录。