iOSはshellスクリプトで一括的にプロパティを変更します。


背景
会社は一連のシェルバージョンを作る必要があります。もしシェルバージョンの内容が同じなら、App Storeに提出すると拒否されるリスクがあります。前の記事で述べたシェルバージョンに混同コードを注入して、アップルから内容があまりにも似ていることが検出され、審査が拒否されることを防止します。もう一つの可能な方法は、ソースファイルのクラス名、属性、方法名などを大量に修正して、バイナリファイルにマークの情報を残して、アップルのマシンを回って審査することです。
この記事では、スクリプトを使って属性名を一括的に変更する方法を紹介しています。その後、スクリプトを使ってクラス名や方法名などの情報を大量に修正する文章もあります。
結果
以下はスクリプトを実行して属性を置換した結果図です。スクリプトはすべての代替属性をabc拡張子に追加しました。もちろん、通常のコンパイルで実行できます。
ソースコード:https://gitee.com/dhar/YTTInjectedContentKit

分析
原理分析
objcコードのクラス名、属性、方法、ソースファイルのパスなどの情報は最終的にバイナリファイルにパッケージされ、バイナリファイルに保存されます。sym記号のセグメントでは、Objdump-tコマンドを使用してバイナリ記号情報を確認します。以下のコマンドは、Objdump-tの結果をファイルInjectContht Kitに書き込みます。Example_Symborsに行きます。

objdump -t InjectedContentKit_Example > InjectedContentKit_Example_Symbols
ファイルの内容が大きいので、いくつかの代表的な内容説明を選択しました。

0000000100026350 l d __TEXT,__text	__text
#                  
0000000000000000 l d *UND*	/Users/aron/PuTaoWorkSpace/project/sscatch/DevPods/InjectedContentKit/InjectedContentKit/Classes/Composer/PubSearchDataComposer.h

#            var  
0000000000000000 l d *UND*	_OBJC_IVAR_$_TextCardItem._title
0000000000000000 l d *UND*	_OBJC_IVAR_$_TextCardItem._showReact
0000000000000000 l d *UND*	_OBJC_IVAR_$_TextCardItem._topChart
0000000000000000 l d *UND*	_OBJC_IVAR_$_TextCardItem._reaction

#              getter    
00000001000264a0 l  F __TEXT,__text	-[TextCardItem title]
00000001000264c0 l  F __TEXT,__text	-[TextCardItem showReact]
00000001000264f0 l  F __TEXT,__text	-[TextCardItem topChart]
0000000100026510 l  F __TEXT,__text	-[TextCardItem setTopChart:]

#              setter    
00000001000028a0 l  F __TEXT,__text	-[SSCatchInviteScheduler setOrganizer:]
00000001000028e0 l  F __TEXT,__text	-[SSCatchInviteScheduler setInputCardBack:]
0000000100002920 l  F __TEXT,__text	-[SSCatchInviteScheduler setInputTextBack:]

#                
0000000000000000 l d *UND*	PubSearchDataComposer.m
000000005a937587 l d __TEXT,__stub_helper	__stub_helper
00000001000251c0 l d __TEXT,__text	__text
上記から分かるように、バイナリには多くの情報が保存されています。ソースコードとは大きく関係しています。簡単な推測をします。アップルのバックグラウンドマシンはバイナリを審査する時に、バイナリの中の記号を通して対比します。もし二つのバイナリ(メインバージョン、シェルバージョン)コードの中の符号の重なりがある閾値を超えたら、これはシェルバージョンをリリースする行為であると判断します。これはアップルが許可しないと言っていますので、実行可能な方法はソースファイルの中のこれらの情報を修正してアップルの審査メカニズムを回避することです。
また、アップルはコードの流れによっては判断されないはずです。バイナリのコントロールプロセスはマシンコードになっています。逆コンパイルというのはアセンブリコードです。少しでもバイナリを変えると大きく変わります。この点から判断するのは難しいです。
ステップ分析
主に以下のステップがあります。
  • は、代替が必要なソースファイルのすべての属性を見つけ、処理後、プロファイルに保存する
  • ユーザがブラックリスト設定ファイルをカスタマイズする
  • ある部分から隔離が必要なコードの中の属性は、ブラックリスト構成ファイル
  • を生成する。
  • は、代替が必要なソースファイルのすべてのマッチング属性を一括的に置換する。
  • ここではなぜ第一歩はプロファイルに保存する必要があるのかを説明します。第三段階の操作は部分的には第一歩と同じです。この部分は単独で一つのモジュールで共有して、一つのフォルダを入力して、最終的に指定されたファイルに保存します。後のコードにこの部分が見られます。
    実現する
    シングルステップ実現
    1、代替が必要なソースファイルのすべての属性を探して、処理後に設定ファイルに保存します。
    このステップの機能は、クライアントが処理するソースフォルダを入力し、ソースフォルダを経由してすべてのソースファイル(.h.mファイル)を取得します。正規のマッチングを使って属性名を見つけて、しばらく配列に保存して、最後にブラックリストのフィルタリング、フィルタリング、その他のフィルタリング条件を経て、最終的に処理対象の属性をクライアント入力の出力ファイルに保存します。
    いくつかのステップに分解できます。
  • 再帰的にフォルダを巡回してソースファイルを取得します。
  • 正則はソースファイルの属性にマッチしています。
  • フィルタ属性(オプション)
  • ファイルに属性を保存します。
  • この部分の機能のソースコードは以下の通りです。
    ファイル名:GetAndStreProperties.sh
    スクリプトは複数の場所で有用なので、別のモジュールとしていくつかのパラメータを定義して、異なるアプリケーションシーンに適応します。下に台本を使っているところが見えます。
    
    #!/bin/bash
    ########################
    #     :                  
    #      -i       
    #      -o      
    #      -f                 
    #      -c          
    ########################
    
    #######     
    param_input_dir=""
    param_output_file=""
    param_custom_filter_file=""
    param_should_use_filter=0
    
    #######     
    while getopts :i:o:c:f opt
    do
    	case "$opt" in
    		i) param_input_dir=$OPTARG
    			echo "Found the -i option, with parameter value $OPTARG"
    			;;
    		o) param_output_file=$OPTARG
    			echo "Found the -o option, with parameter value $OPTARG"
    			;;
    		c) param_custom_filter_file=$OPTARG
    			echo "Found the -c option, with parameter value $OPTARG"
    			;;
    		f) echo "Found the -f option" 
    			param_should_use_filter=1
    			;;
    		*) echo "Unknown option: $opt";;
    	esac
    done
    
    
    #######   
    
    #          
    blacklist_cfg_file="$(pwd)/DefaultBlackListPropertiesConfig.cfg"
    
    #######     
    
    #           
    declare -a implement_source_file_array
    implement_source_file_count=0
    
    
    #          
    declare -a tmp_props_array
    props_count=0
    
    
    # mark: p384
    #             .m  
    function read_source_file_recursively {
    	echo "read_implement_file_recursively"
    	if [[ -d $1 ]]; then
    		for item in $(ls $1); do
    			itemPath="$1/${item}"
    			if [[ -d $itemPath ]]; then
    				#   
    				echo "     ${itemPath}"
    				read_source_file_recursively $itemPath
    				echo "      ====="
    			else 
    				#   
    				echo "     ${itemPath}"
    				if [[ $(expr "$item" : '.*\.m') -gt 0 ]] || [[ $(expr "$item" : '.*\.h') -gt 0 ]]; then
    					echo ">>>>>>>>>>>>mmmmmmm"
    					implement_source_file_array[$implement_source_file_count]=${itemPath}
    					implement_source_file_count=$[ implement_source_file_count + 1 ];
    				fi
    				echo ""
    			fi
    		done
    	else
    		echo "err:      "
    	fi
    }
    
    
    #         ,      
    #    :       
    function get_properties_from_source_file {
    	local class_file=$1;
    	echo "class_file=${class_file}"
    
    	properties=$(grep "@property.*" ${class_file})
    	IFS_OLD=$IFS
    	IFS=$'
    ' for prop_line in $properties; do echo ">>>>>${prop_line}" asterisk_seperator_pattern="\*" if [[ ${prop_line} =~ ${asterisk_seperator_pattern} ]]; then # string prop_name=${prop_line##*${asterisk_seperator_pattern}} # string seal_pattern=";*" seal_pattern_replacement="" prop_name=${prop_name//${seal_pattern}/${seal_pattern_replacement}} subsring_pattern="[ |;]" replacement="" prop_name=${prop_name//${subsring_pattern}/${replacement}} if [[ ${param_should_use_filter} -gt 0 ]]; then grep_result=$(grep ${prop_name} ${blacklist_cfg_file}) echo "grep_result = >>${grep_result}<<" custom_grep_result="" if [[ -n ${param_custom_filter_file} ]]; then custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file}) fi if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then echo "--${prop_name}-- " else echo "--${prop_name}--XXX " tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] echo ">>>>>>>result_prop_name=${prop_name}" fi else tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] fi fi done IFS=$IFS_OLD } # , function get_properties_from_source_dir { local l_classed_folder=$1 echo " ... ${l_classed_folder}" # read_source_file_recursively ${l_classed_folder} echo " ..." for(( i=0;i<${#implement_source_file_array[@]};i++)) do class_file=${implement_source_file_array[i]}; echo " :${class_file}" get_properties_from_source_file ${class_file} done; } # # 、 、 # , (DefaultBlackListPropertiesConfig.cfg ) function post_get_properties_handle { local prop_config_file=$1 # echo "# Properties Configs" > ${prop_config_file} for key in $(echo ${!tmp_props_array[*]}) do # echo "$key : ${tmp_props_array[$key]}" echo ${tmp_props_array[$key]} >> ${prop_config_file} done # cfg_back_file="${prop_config_file}.bak" mv ${prop_config_file} ${cfg_back_file} sort ${cfg_back_file} | uniq > ${prop_config_file} # if [[ ${param_should_use_filter} -gt 0 ]]; then mv ${prop_config_file} ${cfg_back_file} echo "# Properties Configs Filtered" > ${prop_config_file} IFS_OLD=$IFS IFS=$'
    ' # lastLine=""; for line in $(cat ${cfg_back_file} | sed 's/^[ \t]*//g') do if [[ ${#line} -le 6 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then # 6 echo "less then 6 char line or comment line" else if [[ -n ${lastLine} ]]; then # # , if [[ ${line} =~ ${lastLine} ]]; then echo "${line} ${lastLine} " else echo ${lastLine} >> ${prop_config_file} fi fi # lastLine=${line} fi done IFS=${IFS_OLD} fi # rm -f ${cfg_back_file} } get_properties_from_source_dir ${param_input_dir} post_get_properties_handle ${param_output_file}
    以上のスクリプトを使って生成されたプロファイルPrpertiesConfigs.cfgの部分は以下の通りです。
    
    # Properties Configs Filtered
    UserRestrictionLabel
    aboutusButton
    activitySamplers
    addAddressPress
    addressSamplers
    addressTextBox
    appealPress
    appliedGroupedSamplers
    appliedSamplers
    applyPress
    asyncArray
    asyncListSampler
    audioPlayer
    2.ユーザーがブラックリストの設定ファイルをカスタマイズする
    実際のプロセスでは、属性を置き換える記号は、システムクラスの属性を置き換える場合があります。
  • AppDelegateのwindow属性を交換してコンパイルリンクを招いたのは間違いないですが、インタフェースが出られなくなりました。最初のwindowオブジェクトが見つからないからです。
  • UButtonのtitleLabel属性を置換しました。直接コンパイルエラーを引き起こしました。
  • このような問題に対しては、ブラックリストにデフォルトのフィルタ属性を配置する必要があります。ブラックリストのこれらの属性は処理しなくてもいいです。私のビジネスシーンでは、ブラックリストファイルの構成は以下の通りです。
    ファイル名:Default BlackListPropertiesConfig.cfg
    
    # BlackListPropertiesConfig.cfg
    #        ,                 
    window
    name
    title
    titleLabel
    layout
    appealSamplers
    Get AndStreProperties.shスクリプトで使用されているコードの断片は以下の通りです。実はgrepコマンドを使って検索しています。判定時に見つけられました。もしあるなら処理しません。具体的には上から提供された完全なGetAndStreProperties.shスクリプトコードを見ることができます。
    
    if [[ ${param_should_use_filter} -gt 0 ]]; then
    	grep_result=$(grep ${prop_name} ${blacklist_cfg_file})
    	echo "grep_result = >>${grep_result}<<"
    	custom_grep_result=""
    	if [[ -n ${param_custom_filter_file} ]]; then
    		custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file})
    	fi
    	if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then
    		echo "--${prop_name}--       "
    	else
    		echo "--${prop_name}--XXX        "
    
    		tmp_props_array[$props_count]=$prop_name
    		props_count=$[ props_count + 1 ]
    		echo ">>>>>>>result_prop_name=${prop_name}"
    	fi
    else
    	tmp_props_array[$props_count]=$prop_name
    	props_count=$[ props_count + 1 ]
    fi	
    3.ある部分から隔離が必要なコードの中の属性はブラックリストの設定ファイルを生成する。
    この部分の機能は、GetAndStreProperties.shというスクリプトを呼び出し、最終的にファイル出力のファイルをユーザーがカスタマイズしたブラックリスト属性ファイルに追加的に書き込みます。
    
    #...
    #       
    declare -a custom_blacklist_search_dirs
    custom_blacklist_search_dirs=("/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/SSCatchAPI" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Categories" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Components" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/External" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/HandyTools" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Macros" )
    # ...
    
    #          
    custom_blacklist_cfg_file="$(pwd)/CustomBlackListPropertiesConfig.cfg"
    
    # ...
    #                   
    echo "" > ${custom_blacklist_cfg_file}
    for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do
    	custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]}
    	./GetAndStoreProperties.sh \
    		-i ${custom_blacklist_search_dir}\
    		-o ${custom_blacklist_cfg_tmp_file}
    	cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file}
    done
    #...
    最終的に生成されたユーザー定義のブラックリストファイルの一部は以下の通りです。
    ファイル:Custoom BlackListPropertiesConfig.cfg
    
    # Properties Configs
    DBFilePath
    ValidityString
    accessQueue
    age
    attributedNameString
    avatarURLString
    avatarUrlString
    backColorString
    bodyScheduler
    bodyView
    catchDateString
    cellHeight
    channelKey
    cityName
    conditionString
    # ....
    4.交換が必要なソースファイルのすべてのマッチした属性を一括で置換します。
    このステップは前の3つの要素に基づいて、ソースディレクトリのPrpertiesConfigs.cfgプロファイルに現れた属性と属性の参照を検索し、代わりにsedコマンドを使用してgrepコマンドを使用して検索します。スクリプトコードは以下の通りです
    
    #!/bin/bash
    #        
    
    #######   
    # classes   
    classes_dir="$(pwd)/../InjectedContentKitx"
    #       
    declare -a custom_blacklist_search_dirs
    custom_blacklist_search_dirs=("/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/SSCatchAPI" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Categories" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Components" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/External" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/HandyTools" 
    	"/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Macros" )
    #     
    cfg_file="$(pwd)/PropertiesConfigs.cfg"
    #          
    blacklist_cfg_file="$(pwd)/DefaultBlackListPropertiesConfig.cfg"
    #          
    custom_blacklist_cfg_file="$(pwd)/CustomBlackListPropertiesConfig.cfg"
    custom_blacklist_cfg_tmp_file="$(pwd)/TmpCustomBlackListPropertiesConfig.cfg"
    #     ,          
    class_prefix=""
    #     
    class_suffix="abc"
    
    
    #         ,      
    checkOrCreateFile() {
    	file=$1
    	if [[ -f $file ]]; then
    		echo "          $file"
    	else
    		echo "       $file"
    		touch $file
    	fi
    }
    
    #       
    checkOrCreateFile $cfg_file
    
    #           
    function checkInputDestDir {
    	echo -n "          : "
    	read path
    	if [[ -d $path ]]; then
    		classes_dir=$path
    	else
    		echo -n "       ,"
    		checkInputDestDir
    	fi
    }
    
    #          
    if [[ -d $classes_dir ]]; then
    	echo "          $classes_dir"
    else
    	echo "               $classes_dir"
    	checkInputDestDir
    fi
    
    
    #######     
    
    #         
    declare -a rename_properties_config_content_array
    cfg_line_count=0
    
    
    #         
    function read_rename_properties_configs {
    	IFS_OLD=$IFS
    	IFS=$'
    ' # https://www.jb51.net/article/57972.htm for line in $(cat $cfg_file | sed 's/^[ \t]*//g') do is_comment=$(expr "$line" : '^#.*') echo "line=${line} is_common=${is_comment}" if [[ ${#line} -eq 0 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then echo "blank line or comment line" else rename_properties_config_content_array[$cfg_line_count]=$line cfg_line_count=$[ $cfg_line_count + 1 ] # echo "line>>>>${line}" fi done IFS=${IFS_OLD} } function print_array { # local newarray newarray=($(echo "$@")) for (( i = 0; i < ${#newarray[@]}; i++ )); do item=${newarray[$i]} echo "array item >>> ${item}" done } # function rename_properties { # read_rename_properties_configs # print_array ${rename_properties_config_content_array[*]} # for (( i = 0; i < ${#rename_properties_config_content_array[@]}; i++ )); do original_prop_name=${rename_properties_config_content_array[i]}; result_prop_name="${class_prefix}${original_prop_name}${class_suffix}" sed -i '{ s/'"${original_prop_name}"'/'"${result_prop_name}"'/g }' `grep ${original_prop_name} -rl ${classes_dir}` echo " ${original_prop_name}....." done } checkOrCreateFile ${custom_blacklist_cfg_tmp_file} # echo "" > ${custom_blacklist_cfg_file} for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]} ./GetAndStoreProperties.sh \ -i ${custom_blacklist_search_dir}\ -o ${custom_blacklist_cfg_tmp_file} cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file} done # ./GetAndStoreProperties.sh \ -i ${classes_dir}\ -o ${cfg_file}\ -f \ -c ${custom_blacklist_cfg_file} # rename_properties echo "done."
    締め括りをつける
    以上はshellスクリプトに基づいて、シェルバージョンをシーンとして、属性のロットを交替して半自動化の実現ステップを作りました。