iOS uses Shell script to batch modify class names

background

The company needs to make a series of shell versions. If the shell versions are submitted to the App Store with the same content, there is a risk of rejection. In the previous two articles, iOS uses shell scripts to inject obfuscated content and iOS uses shell scripts to modify properties in batches. This is a way to bypass the machine review in Apple's background. This article is still in this business scenario. It introduces the use of scripts to rename class names in batches and strengthen the shell version.

A series of articles on shell combat
iOS uses shell scripts to inject obfuscated content iOS uses shell
scripts to batch modify class names

result

Instructions

  • Open test project

The test project is located in the directory under the project directory , just DevPods/InjectedContentKit/Example/open itInjectedContentKit.xcworkspace

  • Excuting an order

Enter the subdirectory under the project directory on the command line, DevPods/InjectedContentKit/Example/injectContentShellthe corresponding directory on my computer is /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell, and then execute the ./RenameClasses.shscript file to batch modify the class name

➜  injectContentShell git:(master) pwd
/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell
➜  injectContentShell git:(master) ./RenameClasses.sh 
目录存在 /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/../injectedContentKit/Business
// .... 省略
new_file_path = /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/../InjectedContentKit/Business/Pay/XYZSSCatchPayMethodSchedulerABC.m
批量重命名类名完成。

The following is the result of executing the script to replace the class name. The script adds XYZ prefix and ABC suffix to all the classes that need to be replaced, and the running project can still compile and run normally. If it encounters abnormality, fails to compile or crashes, generally write the problematic class into the blacklisted configuration file, so as to exclude the problematic class when running the script next time.

The demo code of this article YTTInjectedContentKit

Class name modification result

Do not submit the modified content to the version management system. First, use the discard function of SVN or GIT to discard the modification, and execute the DeleteClasses.shscript to delete the temporary file.

➜  injectContentShell git:(master) ✗ ./DeleteClasses.sh 
目录存在 /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/InjectedContentKit/Business
删除文件>>>/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/InjectedContentKit/Business/Pay/XYZSSCatchPayMethodSchedulerABC.h
删除文件>>>/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/InjectedContentKit/Business/Pay/XYZSSCatchPayMethodSchedulerABC.m
删除文件>>>/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/InjectedContentKit/Business/XYZSSCatchInviteSchedulerABC.h
删除文件>>>/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/InjectedContentKit/Business/XYZSSCatchInviteSchedulerABC.m

analyze

Principle analysis

For specific principle analysis, you can check this article of mine. iOS uses shell script to modify attributes in batches , which essentially bypass Apple's background machine audit by modifying symbols.

step analysis

There are mainly the following steps, which are similar to the steps in the article to modify properties in batches using shell scripts in iOS , but there are some differences in some details.

  1. Find all the classes in the source file that need to be replaced, and save them in the configuration file after processing
  2. User-defined blacklist configuration file
  3. Batch replace all matching class name information in the source file that needs to be replaced
  4. Modify the name of the class source file

accomplish

single-step implementation

1. Find all the classes in the source file that need to be replaced, and save them in the configuration file after processing

The function of this step is that the client specifies a source code folder to be processed, and recursively traverses the source code folder to obtain all source code files. The found class names are temporarily saved in the array, and finally filtered by blacklist, deduplication, and other filter conditions, and finally the class names to be processed are saved to the output file specified by the customer.

The source code of this part of the function is as follows:
File name: GetAndStoreClasses.sh

#!/bin/bash
########################
# 脚本功能:生成重命名的类的配置脚本
# 输入参数 -i 输入的文件夹
# 输入参数 -o 保存的文件
########################


####### 参数定义
param_input_dir=""
param_output_file=""


####### 参数解析
echo "参数>>${@}"
while getopts :i:o: 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"
			;;
		*) echo "Unknown option: $opt";;
	esac
done
echo "param_input_dir = ${param_input_dir}"
echo "param_output_file = ${param_output_file}"


####### 配置

# 属性黑名单配置文件
blacklist_cfg_file="$(pwd)/DefaultClassesBlackListConfig.cfg"


####### 数据定义

# 定义保存需要处理目标文件的数组
declare -a implement_source_file_array
declare -a implement_source_file_name_array
implement_source_file_count=0

# mark: p384
# 递归函数读取目录下的所有.m文件
function read_implement_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_implement_file_recursively $itemPath
				echo "处理目录结束====="
			else 
				# 文件
				echo "处理文件 ${itemPath}"
				if [[ $(expr "$item" : '.*\.m') -gt 0 ]]; then
					echo ">>>>>>>>>>>>mmmmmmm"
					implement_source_file_array[$implement_source_file_count]=${itemPath}
					class_name=${item//".m"/""};
					implement_source_file_name_array[$implement_source_file_count]=${class_name}
					implement_source_file_count=$[ implement_source_file_count + 1 ];
				fi
				echo ""
			fi
		done
	else
		echo "err:不是一个目录"
	fi
}

post_implement_file_handle() {
	local wirte_to_file=$1
	# 写入文件中
	echo "# 需要处理的类配置文件" > ${wirte_to_file}
	for(( i=0;i<${#implement_source_file_name_array[@]};i++)) 
	do 
		class_file_name=${implement_source_file_name_array[i]}; 
		echo ${class_file_name} >> ${wirte_to_file}
	done;

	# 去重
	wirte_to_file_bak="${wirte_to_file}.bak"
	mv ${wirte_to_file} ${wirte_to_file_bak}
	sort ${wirte_to_file_bak} | uniq > ${wirte_to_file}

	# 过滤
	mv ${wirte_to_file} ${wirte_to_file_bak}
	echo "# Properties Configs Filtered" > ${wirte_to_file}
	IFS_OLD=$IFS
	IFS=$'\n'
	# 上一行的内容
	lastLine="";
	for line in $(cat ${wirte_to_file_bak} | sed 's/^[ \t]*//g')
	do
		grep_result=$(grep ${line} ${blacklist_cfg_file})
		category_judge_substring="\+"
		if [[ ${#line} -le 6 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]] || [[ -n ${grep_result} ]] || [[ ${line} =~ ${category_judge_substring} ]]; 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} >> ${wirte_to_file}
				fi
			fi
			# 更新上一行
			lastLine=${line}
		fi	
	done
	IFS=${IFS_OLD}

	# 删除临时文件
	rm -f ${wirte_to_file_bak}
}

read_implement_file_recursively ${param_input_dir}
post_implement_file_handle ${param_output_file}


The RenameClasses.cfgsection as follows:

# Properties Configs Filtered
AppDelegate
CatchRecordViewController
SSCatchAboutUsScheduler
SSCatchAccountHandler
SSCatchActivityManager
SSCatchActivityScheduler
SSCatchAddressListOrganizer
SSCatchAddressListScheduler

2. User defines a blacklist configuration file

In the process of practice, the attribution of some class files is not clear. For example, some class files belonging to the business are placed in the technical classification folder, which is dependent on the business class files, but not when renaming the class. When this classification file is operated, and the business class file he depends on changes, a compilation error will occur. For this kind of problem, one method is to adjust the class position, or some refactoring is required, which is troublesome; another simple method is to configure some default filtering items in the blacklist. It can be processed. In my business scenario, the configuration of the blacklist file is as follows:

File name: DefaultClassesBlackListConfig.cfg

# DefaultClassesBlackListConfig.cfg
SSCatchRequest
SSCatchCouponSampler
SSCatchRoomRecordTiler
SSCatchColorFormatter

3. Batch replace all matching class name information in the source file to be replaced

In this step, on the basis of the previous two parts, find and replace the classes and class references that appear in the RenameClasses.cfgconfiguration find and use grepcommands, and replace the used sedcommands. The script code is as follows

# 2、根据规则修改类名
function add_prefix_suffix_to_class {
	# 2.1、类添加前缀和后缀
	for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
		original_class_name=${config_content_array[i]};
		result_class_name="${class_prefix}${original_class_name}${class_suffix}"

		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${pbxproj_dir}`
		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${class_name_replace_dir}`

		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${class_name_replace_support_dir}`

		echo "正在处理类 ${original_class_name}..."
	done
}

4. Modify the name of the class source file

Use the mvcommand to rename RenameClasses.cfgclasses that appear in the configuration file

# 3、修改类的文件名称
function rename_class_file {
	echo "class_name_replace_dir >> ${class_name_replace_dir}"
	for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
		
		original_class_name=${config_content_array[i]};
		result_class_name="${class_prefix}${original_class_name}${class_suffix}"

		# .h 文件
		pattern="${original_class_name}\.h"
		find_result=$(find ${class_name_replace_dir} -name ${pattern})
		new_file_path=${find_result/$original_class_name/$result_class_name}
		echo "find_result = ${find_result}"
		echo "new_file_path = ${new_file_path}"
		mv -f $find_result $new_file_path

		# .m 文件
		pattern="${original_class_name}\.m"
		find_result=$(find ${class_name_replace_dir} -name ${pattern})
		new_file_path=${find_result/$original_class_name/$result_class_name}
		echo "find_result = ${find_result}"
		echo "new_file_path = ${new_file_path}"
		mv -f $find_result $new_file_path

	done
}

Script to completely rename a class

#!/bin/bash
########################
# 脚本功能:类重命名脚本
# 输入参数 -i 输入的文件夹
# 输入参数 -o 保存的文件
########################


####### 配置
# 定义需要替换的类的查找目录,作为参数传递给GenRenameClasses.sh脚本使用,最终获取到的类名称保存到配置文件中,以给后续步骤使用
class_search_dir="$(pwd)/../injectedContentKit/Business000"
# class_search_dir="$(pwd)/../injectedContentKit/Business"
class_search_dir="/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Business"
# 配置文件
cfg_file="$(pwd)/RenameClasses.cfg"

# project.pbxproj文件目录,需要替换该配置文件中的类的名称配置
pbxproj_dir="$(pwd)/../InjectedContentKit.xcodeproj000"
# pbxproj_dir="$(pwd)/../InjectedContentKit.xcodeproj"
pbxproj_dir="/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch.xcodeproj"

# 定义该目录下的文件需要进行批量替换处理
class_name_replace_dir="$(pwd)/../InjectedContentKit000"
# class_name_replace_dir="$(pwd)/../InjectedContentKit"
class_name_replace_dir="/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Business"

# 定义该目录下的文件需要进行批量替换处理
class_name_replace_support_dir="$(pwd)/../InjectedContentKit000"
# class_name_replace_support_dir="$(pwd)/../InjectedContentKit"
class_name_replace_support_dir="/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/SupportingFiles"


# 类前缀
class_prefix="XYZ"
# 类后缀
class_suffix="ABC"


####### 配置检查处理

# 导入工具脚本
. ./FileUtil.sh

# 检测 class_search_dir
checkDirCore $class_search_dir "指定类的查找目录不存在"
class_search_dir=${CheckInputDestDirRecursiveReturnValue} 

# 检测 pbxproj_dir
checkDirCore $pbxproj_dir "指定项目配置目录(pbxproj所在的目录,一般是 xxx.xcodeproj)不存在"
pbxproj_dir=${CheckInputDestDirRecursiveReturnValue}

# 检测 class_name_replace_dir
checkDirCore $class_name_replace_dir "指定类名称修改的目录不存在"
class_name_replace_dir=${CheckInputDestDirRecursiveReturnValue}

# 检测 class_name_replace_support_dir
checkDirCore $class_name_replace_support_dir "指定类名称修改的目录不存在"
class_name_replace_support_dir=${CheckInputDestDirRecursiveReturnValue}


# 检测或者创建配置文件
checkOrCreateFile $cfg_file


####### 数据定义
# 定义保存类名称的数组
declare -a config_content_array
cfg_line_count=0

# 1、读取配置文件内容保存到数组中
read_config_content_to_array() {
	# 读取配置文件
	echo "开始读取配置文件..."
	# mark: p291
	IFS_OLD=$IFS
	IFS=$'\n'
	# 删除文件行首的空白字符 http://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
			config_content_array[$cfg_line_count]=$line
			cfg_line_count=$[ $cfg_line_count + 1 ]
			echo "line>>>>${line}"
		fi	
	done
	IFS=${IFS_OLD}

	for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
		config_content=${config_content_array[i]};
		echo "config_content>>>>>>${config_content}"
	done
}


# 2、根据规则修改类名
function add_prefix_suffix_to_class {
	# 2.1、类添加前缀和后缀
	for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
		original_class_name=${config_content_array[i]};
		result_class_name="${class_prefix}${original_class_name}${class_suffix}"

		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${pbxproj_dir}`
		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${class_name_replace_dir}`

		sed -i '{
			s/'"${original_class_name}"'/'"${result_class_name}"'/g
		}' `grep ${original_class_name} -rl ${class_name_replace_support_dir}`

		echo "正在处理类 ${original_class_name}..."
	done
}

# 3、修改类的文件名称
function rename_class_file {
	echo "class_name_replace_dir >> ${class_name_replace_dir}"
	for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
		
		original_class_name=${config_content_array[i]};
		result_class_name="${class_prefix}${original_class_name}${class_suffix}"

		# .h 文件
		pattern="${original_class_name}\.h"
		find_result=$(find ${class_name_replace_dir} -name ${pattern})
		new_file_path=${find_result/$original_class_name/$result_class_name}
		echo "find_result = ${find_result}"
		echo "new_file_path = ${new_file_path}"
		mv -f $find_result $new_file_path

		# .m 文件
		pattern="${original_class_name}\.m"
		find_result=$(find ${class_name_replace_dir} -name ${pattern})
		new_file_path=${find_result/$original_class_name/$result_class_name}
		echo "find_result = ${find_result}"
		echo "new_file_path = ${new_file_path}"
		mv -f $find_result $new_file_path

	done
}

# 获取需要重命名的类名称,保存到配置文件中
./GetAndStoreClasses.sh\
	-i ${class_search_dir}\
	-o ${cfg_file}

# 读取配置文件内容保存到数组中
read_config_content_to_array
# 类名以及引用添加前缀和后缀
add_prefix_suffix_to_class
# 修改类文件名字
rename_class_file

echo "批量重命名类名完成。"

Delete the renamed class file

One thing to pay attention to here is not to submit the renamed class to SVN or Git. After using it, you can use the Revert function to roll back the code, because your configuration file will be invalid after submission and need to be reorganized . Our company uses SVN, and the renamed files will not be reverted after the revert operation, so with this script, the renamed files can be deleted in batches.

#!/bin/bash
########################
# 脚本功能:删除类重命名脚本产生生成的类
########################

####### 配置
# 定义需要替换的类的查找目录,作为参数传递给GenRenameClasses.sh脚本使用,最终获取到的类名称保存到配置文件中,以给后续步骤使用
class_search_dir="$(pwd)/../injectedContentKit/Business000"
class_search_dir="/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Business"

# 类前缀
class_prefix="XYZ"
# 类后缀
class_suffix="ABC"


####### 配置检查处理
# 导入工具脚本
. ./FileUtil.sh

# 检测 class_search_dir
checkDirCore $class_search_dir "指定类的查找目录不存在"
class_search_dir=${CheckInputDestDirRecursiveReturnValue} 

# 也可以使用$(grep -rl "XYZ.*ABC" ${class_search_dir})
for class_file_path in `grep -rl "XYZ.*ABC" ${class_search_dir}`; do
	echo "删除文件>>>${class_file_path}"
	rm -f ${class_file_path}
done

Summarize

The above is a semi-automatic implementation step for batch replacement of classes based on the shell script and the shell version. If there is something wrong, please do not hesitate to let me know.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324398606&siteId=291194637