iOS證書籤名機制&重簽名&防止重簽名

SimonYe發表於2019-12-04

iOS簽名機制

概念

相比安卓系統隨便從哪裡下載都能執行,系統安全存在許多隱患,例如盜版軟體、病毒入侵、靜默安裝等等。蘋果為了控制手機上的所有應用必須是經過授權的,規定正規應用只能在AppStore下載,同時對於除錯階段的安裝、灰度測試和企業分發,蘋果使用雙重簽名機制來控制應用許可權並使其不被濫用。先介紹一些概念便於後續的理解。

非對稱加密

非對稱加密則兩份金鑰,分別是公鑰和私鑰,用公鑰加密的資料,要用私鑰才能解密,用私鑰加密的資料,要用公鑰才能解密。正常情況下,公鑰可以對外公開,而私鑰需私密儲存。我們常用公鑰加密私鑰解密的方式來進行加密防止資料明文傳輸,用私鑰加密公鑰解密的方式來簽名確保資料的完整性防止被篡改。

數字摘要

將任意長度的源文字通過Hash函式計算得到一竄固定長度的文字,要保證不同源文字計算得到的值各不相同,且不能被反推得到源文字。常用的Hash演算法有MD5和SHA。

數字簽名

又稱公鑰數字簽名,是非對稱加密與數字摘要的結合,用於驗證資料的完整性及不可抵賴性。傳送方用接收方的公鑰對摘要進行加密後和報文一起傳送,接受方接受後用相同的Hash演算法對報文進行摘要計算,接著用自己的私鑰解密得到傳送方的摘要,如果兩個摘要相同則接收方就能確認該報文是對方發出的且未被篡改。

其他

名稱 作用
證書(cer) 內容是公鑰或私鑰,由其他機構對其簽名組成的資料包。
Entitlements 包含了 App 許可權開關列表。
CertificateSigningRequest 本地公鑰。
p12 本地私鑰,可以匯入到其他電腦。
Provisioning Profile 包含了 證書 / Entitlements 等資料,並由蘋果後臺私鑰簽名的資料包。

AppStore簽名

將App提交稽核後,蘋果會用官方的私鑰對我們提交的App重簽名,使用者下載到手機後,iOS裝置內建的公鑰會對App進行驗證,驗證成功即可正常使用。
所以App上傳到AppStore後,就跟我們本地的證書/Provisioning Profile 都沒有關係了,無論是否過期或被廢除,都不會影響 AppStore 上的安裝包。

iOS證書籤名機制&重簽名&防止重簽名

Xcode安裝&Adhoc&In-House

對於非AppStore安裝的應用蘋果採用了雙重簽名的方式,用到了兩對金鑰,Mac電腦金鑰對L,蘋果官方金鑰對A。

  • 我們在進行開發時,會從Mac的keychain 裡的 “從證書頒發機構請求證書生成”CertificateSigningRequest(含有Mac的公鑰L資訊)檔案上傳到蘋果後臺,蘋果會用其私鑰A對公鑰L簽名,並生成一份包含公鑰L資訊和蘋果簽名資訊的開發/釋出證書cer,當我們將cer下載到Mac後,keychain會把CertificateSigningRequest和證書關聯起來。
  • 在蘋果後臺配置AppID,可用裝置IDs(企業證書不需要)和Entitlements,用這些額外資訊和證書再用私鑰A簽名,最後蘋果將證書+額外資訊+簽名組成一個Provisioning Profile檔案(mobileprovision字尾),下載到Mac上。
  • 在Mac上編譯完一個App後,Mac會用私鑰L對App簽名,並將Provisioning Profile檔案也打包到App中,檔名為 embedded.mobileprovision。
  • 安裝時,蘋果通過內建在手機中的公鑰A驗證embedded.mobileprovision中的簽名是否正確,接著驗證證書中的簽名是否正確。
  • 確保了 embedded.mobileprovision 裡的資料都是蘋果授權以後,就可以取出裡面的資料,做各種驗證,包括用公鑰 L 驗證APP簽名,驗證裝置 ID 是否在 ID 列表上,驗證證書是否過期,AppID 是否對應得上,許可權開關是否跟 APP 裡的 Entitlements 對應等等。

如果別的 Mac 也要編譯簽名這個 App,可以將私鑰匯出給其他 Mac 用,在 keychain 裡匯出私鑰,就會存成.p12 檔案,其他 Mac 匯入了這個私鑰後便可用同一套蘋果證書及Provisioning Profile檔案。 In-House安裝不限制裝置ID數。

iOS證書籤名機制&重簽名&防止重簽名

重簽名

完全重簽名

重籤是把已釋出/未釋出的包重新簽名為自己的證書和簽名,關鍵就是替換ipa內的證書和描述檔案。主要通過codesign命令完成。

0 檢視ipa包是否加殼,只有未加殼的包才可以重簽名。

$ otool -l 「Mach-O檔案」 | grep crypt
// 輸出cryptid為0代表已經砸殼,即解密,為1或者2表示以第1類或者第2類加密方案加密。
複製程式碼

1.1 檢視本地證書列表並記錄下要用來簽名的證書名,例如"iPhone Distribution: XXXXX (XXX)"。

$ security find-identity -v -p codesigning
複製程式碼

1.2 新建Xcode工程,用1.1證書編譯後生成新App,將App包裡embedded.mobileprovision檔案取出替換ipa包中的檔案。

2.1 刪除ipa包內部可被重簽名的外掛(PlugIns目錄下)。

2.2 將ipa包內的所有Framework重簽名(Frameworks目錄下)。

$ codesign -fs "iPhone Distribution: XXXXX (XXX)"  xxx.framework
複製程式碼

2.3 檢視Mach-O檔案是否有系統許可權,若沒有則新增許可權。

$ chmod +x 「Mach-O檔案」
複製程式碼

3.1 將ipa包內info.plist的BundleId修改為1.1中工程的BundleId。

3.2 用命令檢視embedded.mobileprovision檔案,找到其中的entitlements欄位,並且複製entitlements欄位和其中的內容。

$ security cms -D -i 「embedded檔案路徑」
複製程式碼

3.3 新建entitlements.plist檔案,將複製內容拷貝到檔案中,然後將entitlements.plist複製到ipa的同級目錄下。

3.4 對App進行重簽名,並壓縮成新的ipa包。

//重簽名
$ codesign -fs "iPhone Distribution: XXXXX (XXX)" --no-strict --entitlements=entitlements.plist

//壓縮ipa包
$ zip -r 「輸出的檔名(.ipa)」 Payload/
複製程式碼

4 將ipa包安裝到手機,若能同時存在兩個應用且能正常執行則表示重簽名成功。

Shell指令碼重簽名

# ${SRCROOT} 它是工程檔案所在的目錄
TEMP_PATH="${SRCROOT}/Temp"
#資原始檔夾,我們提前在工程目錄下新建一個APP資料夾,裡面放ipa包
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

複製程式碼

如何防止應用被重簽名

內容來源於「iOS安全防護之重簽名防護和sysctl反除錯」。

校驗描述檔案資訊

可以在啟動時校驗描述檔案資訊與打包時是否一致。例如判斷組織單位:
先記錄證書中的組織單位資訊。

iOS證書籤名機制&重簽名&防止重簽名
在啟動時檢測是否一致。

void  checkCodesign(NSString *id){
    // 描述檔案路徑
    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;
            range.location = fromPosition;
            range.length = toPosition - fromPosition;
            
            NSString *fullIdentifier = [embeddedProvisioningLines[i+1] substringWithRange:range];
            NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
            NSString *appIdentifier = [identifierComponents firstObject];
       
            // 對比簽名ID
            if (![appIdentifier isEqual:id]) {
                //exit
                asm(
                    "mov X0,#0\n"
                    "mov w16,#1\n"
                    "svc #0x80"
                    );
            }
            break;
        }
    }
}
複製程式碼

sysctl檢測是否被除錯

#import <sys/sysctl.h>
bool checkDebugger(){
    //控制碼
    int name[4];//放位元組碼-查詢資訊
    name[0] = CTL_KERN;//核心檢視
    name[1] = KERN_PROC;//查詢程式
    name[2] = KERN_PROC_PID; //通過程式id查程式
    name[3] = getpid();//拿到自己程式的id
    //查詢結果
    struct kinfo_proc info;//程式查詢資訊結果
    size_t info_size = sizeof(info);//結構體大小
    int error = sysctl(name, sizeof(name)/sizeof(*name), &info, &info_size, 0, 0);
    assert(error == 0);//0就是沒有錯誤
    
    //結果解析 p_flag的第12位為1就是有除錯
    //p_flag 與 P_TRACED =0 就是有除錯
    return ((info.kp_proc.p_flag & P_TRACED) !=0);
   
}
...
...
//檢測異常時退出
if (checkDebugger()) {
        asm("mov X0,#0\n"
            "mov w16,#1\n"
            "svc #0x80"
            );
       
    }
...
複製程式碼

參考連結

iOS App 簽名的原理
iOS逆向(3)-APP重簽名
iOS安全防護之重簽名防護和sysctl反除錯

From:SimonYe

相關文章