iOS Framework混淆/編譯打包腳本(支持swift/oc/c++)

本文將介紹iOS代碼混淆及編譯的自動(dòng)化腳本方案,支持swift、m、c、cpp和h文件類型的混淆,使用編譯時(shí)混淆的策略,不影響源碼閱讀,只需在想要混淆的函數(shù)名或者變量名前加個(gè)private_即可,可通過函數(shù)實(shí)現(xiàn)安全混淆、去混淆、混淆再編譯。
Demo傳送門

??聲明

  1. 請將該腳本放在Xcode的Project工程的根目錄。
  2. 當(dāng)前版本未配置完整Xcode環(huán)境變量,僅支持混淆功能,不支持framework編譯,若需編譯請用Xcode運(yùn)行該腳本。
  3. PS:下一版更新會(huì)支持在終端運(yùn)行腳本。

先祭出shell代碼

#!/bin/bash

#  confuseAndBuild.sh
#  ConfuseSwift
#
#  Created by Jonor on 2018/4/28.
#  Copyright ? 2018年 Zhang. All rights reserved.

# ??聲明
# 1. 請將該腳本放在Xcode的Project工程的根目錄。
# 2. 當(dāng)前版本未配置完整Xcode環(huán)境變量,僅支持混淆功能,不支持framework編譯,若需編譯請用Xcode運(yùn)行該腳本。
# 3. PS:下一版更新會(huì)支持在終端運(yùn)行腳本。

# 認(rèn)為定義了‘PROJECT_NAME’的是從Xcode運(yùn)行,未定義則是從終端運(yùn)行
if [ -z "$PROJECT_NAME" ]; then
    CONFUSE_DIR="."
else
    CONFUSE_DIR="${SRCROOT}/${PROJECT_NAME}"
fi

CONFUSE_PREFIX="private_"

BACKUP_FILE=".backup.log"
SYMBOL_FILE=".symbol.log"
CONFUSE_FILE=".confuse.log"
CONFUSE_FLAG=".confuseFlag"

SOURCE_ARRAY=( "*.swift" 
                "*.m" 
                "*.h" 
                "*.c" 
                "*.cpp")
BACKUP_EXTENSION=".bak"


# 格式:echo -e "\033[背景色;前景色m 打印的字符串 \033[0m" 
# 顏色:重置=0,黑色=30,紅色=31,綠色=32,黃色=33,藍(lán)色=34,洋紅=35,青色=36,白色=37。
# 示例:echo -e “\033[30m 我是黑色字 \033[0m” 
# 參考:https://www.cnblogs.com/xiansong1005/p/7221316.html
#      https://www.cnblogs.com/lr-ting/archive/2013/02/28/2936792.html
info() {
    local green="\033[1;32m"
    local normal="\033[0m"
    echo -e "[${green}info${normal}] $1"
}

error() {
    local red="\033[1;31m"
    local normal="\033[0m"
    echo -e "[${red}error${normal}] $1"
}

# 生成隨機(jī)字符串 16字
randomString() {
    openssl rand -base64 64 | tr -cd 'a-zA-Z' | head -c 16
}

# 獲取符號的隨機(jī)字符串  $1是符號名
randomStringWithSymbol() {
    grep -w $1 $SYMBOL_FILE -h | cut -d \  -f 2
}

removeIfExist() {
    if [ -f $1 ]; then
        rm $1
    fi
}

# 備份文件 $1:file full path
backupFile() {
    file=$1
    # 在原文件名前加個(gè).(點(diǎn)符合)用作備份名
    fileName=${file##*/}
    backupPath=${file/$fileName/.$fileName$BACKUP_EXTENSION}
    echo "backup $file to $backupPath"

    if [ ! -f $backupPath ]; then
        cp $file $backupPath
        echo $backupPath >>$BACKUP_FILE
    fi
}

# 方案1. 精確備份:用關(guān)鍵字遍歷會(huì)修改到的source文件,再將其備份 -- 消耗性能
# 方案2. 整體備份:備份所有source文件 -- 消耗存儲空間
# 根據(jù)需要,為簡單起見,這里選用方案2
backupAllSource() {
    info "backup all swift files"
    NAMES="-name \"${SOURCE_ARRAY[0]}\""
    i=1
    while [ $i -lt ${#SOURCE_ARRAY[@]} ]; do  
        NAMES+=" -or -name \"${SOURCE_ARRAY[$i]}\""
        let i++
    done
    # echo $NAMES

    removeIfExist $BACKUP_FILE
    touch $BACKUP_FILE
    
    eval "find $CONFUSE_DIR $NAMES" | while read file; do
        backupFile $file
    done
}

# 混淆工作, ??該函數(shù)不會(huì)自動(dòng)備份,要備份請調(diào)用safeConfuse函數(shù)
confuseOnly() {
    info "confuse start..."

    # 獲取要混淆的函數(shù)名和變量名
    INCLUDES="--include=\"${SOURCE_ARRAY[0]}\""
    i=1
    while [ $i -lt ${#SOURCE_ARRAY[@]} ]; do  
        INCLUDES+=" --include=\"${SOURCE_ARRAY[$i]}\""
        let i++    
    done
    eval "grep $CONFUSE_PREFIX -r $CONFUSE_DIR $INCLUDES -n" >$CONFUSE_FILE

    # cat $CONFUSE_FILE
    # 綁定隨機(jī)字符串
    removeIfExist $SYMBOL_FILE
    touch $SYMBOL_FILE
    
    cat $CONFUSE_FILE | egrep -w $CONFUSE_PREFIX"[0-9a-zA-Z_]*" -o | sort | uniq | while read line; do
        echo $line" `randomString`" >>$SYMBOL_FILE
    done

    # cat $SYMBOL_FILE

    # 讀取備份文件記錄
    # 在這里沒使用遍歷批量替換,怕文件太多的時(shí)候影響性能
    cat $CONFUSE_FILE | while read line; do
#        echo "> $line"
        # 截取行號
        lineNum=`echo $line | sed 's/.*:\([0-9]*\):.*/\1/g'`
        # 截取文件路徑
        path=${line%%:*}
        
        # 一行可能有多個(gè)要替換的子串,要循環(huán)遍歷完
        # 這里之所以要用`sort -r`倒序是因?yàn)橛袀€(gè)bug:如有字符串"jjyy abc hello abcde", 現(xiàn)在要替換"abc"為"123"(abcde保持不變),也就是傳說中的‘全匹配替換’,
        # 但不知為何在macOS下單詞邊界表達(dá)式不起作用:\<abc\> 或者 \babc\b都不起作用,Linux下這個(gè)正則表達(dá)式是沒問題的。
        # 倒序之后有長串優(yōu)先替換長串,防止短串把長串部分替換掉。但依然存在bug:若是長串不需要替換,則短串替換是依然會(huì)將長串部分替換??
        # 因此依然還需要尋找macOS下單詞邊界/全匹配 的正則表達(dá)式
        echo $line | egrep -w $CONFUSE_PREFIX"[0-9a-zA-Z_]*" -o | sort -r | while read -ra symbol; do
            # 根據(jù)名稱獲取綁定的隨機(jī)字符串
            random=`randomStringWithSymbol $symbol`
#            echo "$path $lineNum $symbol $random"
            # 隨機(jī)字符串替換
            # -i:表示直接在原文件替換,"":表示不要備份
            sed -i "" "${lineNum}s/$symbol/$random/g" $path 

            echo "  $symbol => $random"
        done
    done

    info "confuse done"
}

# 編譯工作,生成通用framework
buildAll() {
    info "build start..."
    
    if [ -z "$PROJECT_NAME" ]; then
        echo -e "\033[1;31mERROR:當(dāng)前版本未配置完整Xcode環(huán)境變量,僅支持混淆功能,不支持framework編譯,若需編譯請用Xcode運(yùn)行該腳本\033[0m"
        return
    fi

    # 要build的target名
    TARGET_NAME=${PROJECT_NAME}
    UNIVERSAL_OUTPUT_DIR="${SRCROOT}/Framework/"

    # 創(chuàng)建輸出目錄,并刪除之前的framework文件
    mkdir -p "${UNIVERSAL_OUTPUT_DIR}"
    rm -rf "${UNIVERSAL_OUTPUT_DIR}/${TARGET_NAME}.framework"

    #分別編譯模擬器和真機(jī)的Framework
    xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} ARCHS="armv7 armv7s arm64" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
    xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} ARCHS="i386 x86_64" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

    #拷貝framework到univer目錄
    cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_DIR}"

    # 合并swiftmodule到univer目錄
    cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule/" "${UNIVERSAL_OUTPUT_DIR}/${TARGET_NAME}.framework/Modules/${TARGET_NAME}.swiftmodule"

    #合并framework,輸出最終的framework到build目錄
    lipo -create -output "${UNIVERSAL_OUTPUT_DIR}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"

    #刪除編譯之后生成的無關(guān)的配置文件
    dir_path="${UNIVERSAL_OUTPUT_DIR}/${TARGET_NAME}.framework/"
    for file in ls $dir_path; do
        if [[ ${file} =~ ".xcconfig" ]]; then
            rm -f "${dir_path}/${file}"
        fi
    done

    #判斷build文件夾是否存在,存在則刪除
    if [ -d "${SRCROOT}/build" ]; then
        rm -rf "${SRCROOT}/build"
    fi

    #rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"

    #打開合并后的文件夾
    open "${UNIVERSAL_OUTPUT_DIR}"

    info "build done"
}

# 清理工作,去混淆
unconfuse() {
    info "clean start..."
    if [ -f $CONFUSE_FLAG ]; then
        # 恢復(fù)混淆的函數(shù)名所在source文件的bak內(nèi)容
        cat $BACKUP_FILE | while read backup; do
            backupName=${backup##*/}
            fileName=`echo $backupName | cut -d "." -f2,3`
            filePath=${backup/$backupName/$fileName}
            
            echo "recover $backup to $filePath"

            cp $backup $filePath
            rm $backup
        done
        # 刪除修改記錄
        removeIfExist $SYMBOL_FILE
        removeIfExist $CONFUSE_FILE
        removeIfExist $BACKUP_FILE
        removeIfExist $CONFUSE_FLAG
    else
        echo "Not confuse yet!"
    fi
    info "clean done"
}

# 檢查是否上次未完成
precheck() {
    # 創(chuàng)建一個(gè)隱藏文件,僅標(biāo)記混淆編譯的狀態(tài)
    # 由于編譯過程有可能被中斷,因此混淆后的代碼可能未恢復(fù),在開始備份前先做判斷
    unconfuse        
    touch $CONFUSE_FLAG
}

# 去混淆->備份->混淆
safeConfuse() {
    precheck
    backupAllSource
    confuseOnly
}

# 去混淆->備份->混淆
# 編譯
# 去混淆
safeConfuseAndBuild() {
    info "preparing confuse and build..."

    safeConfuse
    buildAll
    unconfuse

    info "all done"
}

usage() {
    echo -e "\033[1;31musage: ./confuseAndBuild.sh [-u|c|b|a]"
    echo -e "  -u"
    echo -e "      unconfuse: 清理工作,去混淆"
    echo -e "  -c"
    echo -e "      safeConfuse: 去混淆->備份->混淆"
    echo -e "  -b"
    echo -e "      buildAll: 編譯生成通用framework"    
    echo -e "  -a"
    echo -e "      safeConfuseAndBuild: 去混淆->備份->混淆->編譯->去混淆"
    echo -e "EXAMPLE:"
    echo -e "  ./confuseAndBuild.sh -u\033[0m"
}

main() {
    echo "參數(shù)個(gè)數(shù):$#  參數(shù)值:$1"
    case $1 in
    "-u" )
        unconfuse
        ;;
    "-c" )
        safeConfuse
        ;;
    "-b" )
        buildAll
        ;;
    "-a" )
        safeConfuseAndBuild
        ;;
    * )
        usage
        ;;
    esac
}

main $@

??注意

請勿在混淆中的項(xiàng)目里修改代碼,因?yàn)榛煜龝r(shí)我會(huì)備份所有swift文件,當(dāng)再次運(yùn)行safeConfuse、safeConfuseAndBuild之前我會(huì)恢復(fù)備份文件(如果有的話),因此可能會(huì)覆蓋混淆時(shí)所作的修改。
以下情況會(huì)產(chǎn)生代碼混淆或混淆殘留:
- 運(yùn)行了safeConfuse進(jìn)行混淆;
- 運(yùn)行safeConfuseAndBuild,并且運(yùn)行過程被中斷或編譯時(shí)異常退出;
因此,在你修改代碼前請確保代碼未混淆或運(yùn)行unconfuse以完全解除混淆。

建議看看Demo,內(nèi)有三個(gè)腳本用例Scheme,對應(yīng)以下三個(gè)函數(shù),詳細(xì)請看以上shell文件注釋:

unconfuse() {
# 清理工作,去混淆
# 恢復(fù)混淆的函數(shù)名所在swift文件的bak內(nèi)容
# 刪除修改記錄
}

safeConfuse() {
# 去混淆->備份->混淆
}

safeConfuseAndBuild() {
# 去混淆->備份->混淆
# 編譯
# 去混淆
}

確認(rèn)是否混淆成功

方法一:
使用反匯編工具(如dump,hopper,ida)查看framework,混淆后的‘private_’前綴字樣都變成了隨機(jī)字符串。
方法二:
單獨(dú)運(yùn)行safeConfuse這個(gè)函數(shù),這個(gè)函數(shù)會(huì)將當(dāng)前代碼含‘private_’的字樣進(jìn)行混淆,運(yùn)行結(jié)束你可以看到相應(yīng)的代碼變成了隨機(jī)字符串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 麗姐結(jié)婚了,27歲該結(jié)婚,不算稀罕事兒,但這事兒發(fā)生在了麗姐身上就成了新鮮事,因?yàn)辂惤阍?jīng)深愛著另一個(gè)人,深愛到我...
    夏夏儂艾閱讀 595評論 0 0
  • 天剛麻麻亮,虎子爹就早早起來套好了毛驢,虎子娘把織好的粗布疊好,整齊地碼在驢背上。進(jìn)城要走四五十里的山路,...
    小豬皮杰閱讀 658評論 1 4
  • 雜言詩(詩經(jīng)格式):澧水美兮,沅江嬌兮。于以愛之,彼我之故。高山峻兮,翠竹妭兮。于以愛之,彼我之此。 現(xiàn)代通俗詩(...
    碩果蕾蕾閱讀 1,318評論 8 6

友情鏈接更多精彩內(nèi)容