iOS使用shell腳本注入混淆內容 頂 原 薦

背景

公司需要做一系列的殼版本,殼版本如果內容雷同提交到App Store會有被拒絕的風險,其中有一種解決方案是在殼版本中注入混淆的代碼,防止被蘋果檢測到內容太過雷同而導致審覈被拒絕,本文是針對這個場景,使用shell腳本進行半自動批量添加和刪除混淆代碼。

shell實戰的系列文章
iOS使用shell腳本注入混淆內容
iOS使用Shell腳本批量修改類名稱
iOS使用shell腳本批量修改屬性

結果

使用方法

  • 打開測試工程

測試工程位於項目目錄下面的DevPods/InjectedContentKit/Example/目錄下,打開InjectedContentKit.xcworkspace即可

  • 執行命令

在命令行中進入到項目目錄下面的DevPods/InjectedContentKit/Example/injectContentShell子目錄,在我的電腦對應的目錄爲/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell,然後執行./injectedContentShell.sh腳本文件批量添加混淆內容,腳本開頭的to_process_file_dir變量配置的是需要修改的類所在目錄,腳本會自動檢測該配置是否正確,正確會顯示菜單項頁面,否則會提示用戶輸入需要修改的類所在目錄,輸入正確之後纔會顯示菜單項頁面。菜單項有三個選項,第一個爲刪除注入內容,第二個爲添加註入內容,第三爲退出腳本執行,輸入對應的編號即可執行相應的功能

➜  injectContentShell git:(master) ./injectedContentShell.sh
/usr/local/bin/sed
testresult = 0
lrwxr-xr-x 1 aron admin 29 2 13 10:28 /usr/local/bin/sed -> ../Cellar/gnu-sed/4.4/bin/sed
testresult = 82
檢測到使用gun sed
目錄存在 /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/../injectedContentKit/Business
檢測到配置文件存在 /Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/injectedContentConfig.cfg
開始讀取配置文件...

/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/../injectedContentKit/Business
read_implement_file_recursively
// ......省略


			選項菜單

	1. 刪除注入內容
	2. 添加註入內容
	0. Exit menu


		Enter option: 2

// ......
>>>>>>>
 內容添加完成 

	Hit any key to continue


			選項菜單

	1. 刪除注入內容
	2. 添加註入內容
	0. Exit menu


		Enter option: 1/Users/aron/git-repo/YTTInjectedContentKit/DevPods/InjectedContentKit/Example/injectContentShell/../injectedContentKit/Business/ICKXibTestViewController.m
>>>>>>>[[GameDetailDataComposer new] loadDataWithBlock:nil];
// ......省略
pattern_str = \[\[WriterDataComposer new\] loadWithType:MMLoadTypeMore completionBlock:nil\];
 內容刪除完成 

處理結果

添加註入內容到源碼中的結果

添加註入內容到源碼中

把注入內容從源碼中刪除的結果

把注入內容從源碼中刪除

本文的Demo代碼YTTInjectedContentKit
Fork me on Gitee

分析

在開始做之前,對步驟流程做了一些構思如下:

初始步驟流程

  • 步驟一:手動處理
    混淆注入類列表
    混淆注入類對應的方法列表

  • 步驟二:配置化
    步驟一的形成配置化

  • 步驟三:自動化
    掃描對應的類和類對應的方法列表,形成對應的配置文件
    從配置文件中讀取配置注入到對應的目標類中

  • 步驟四:自動目標文件的處理
    目標文件的查找規則:哪些是需要注入的目標文件
    目標文件的注入規則:目標文件中需要在什麼位置進行注入

  • 步驟五:注入內容自身配置
    注入內容需要在不同環境下變換不同的形態,包括類名,方法名等

後面在實現的過程中發現步驟三和步驟五不好實現,所有簡化了這部分的流程,最終只保留了以下幾個步驟:

優化的步驟流程

  • 步驟一:手動處理
    混淆注入類列表
    混淆注入類對應的方法列表

  • 步驟二:配置化
    步驟一的形成配置化

  • 步驟三:自動目標文件的處理
    目標文件的查找規則:哪些是需要注入的目標文件
    目標文件的注入規則:目標文件中需要在什麼位置進行注入

以及在實現過程中遇到了一些細節需要處理,這些細節部分作爲自步驟如下:

  • 子步驟:
    步驟三-1:檢查時候安裝gun-sed,mac下的sed和gun sed 會有差別,所有統一使用gun sed
    步驟三-2:用戶指定目標位置,需要用戶輸入
    步驟三-3:刪除所有的注入內容

實現

步驟一:手動處理

整理一份注入的內容這部分工作是需要手動處理的,這部分的內容應該是具備自完備性的,可以被多個項目做爲依賴導入,所以把這些內容作爲一個pod庫比較合適,也很方便在pod庫的測試項目中測試該庫的自完備性。庫裏面的內容可以是任意的,在我實踐的過程中,我是把一箇舊的項目的網絡接口模塊作爲了這部分內容,因爲這部分內容相對的比較獨立和容易抽取。

下圖是我從舊的項目中提取的一些類作爲混淆類

目錄結構

以及我在測試工程中測試混淆的接口調用,在測試工程中測試混淆代碼的調用以確保編譯鏈接無誤以及確保庫的自完備性。

#import "ICKViewController.h"
#import <InjectedContentKit.h>

@implementation ICKViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    

    [[GameDetailDataComposer new] loadDataWithBlock:nil];
    
    [[PubSearchDataComposer new] loadSuggestionWithCompletionBlock:nil];
    
    [[WriterDataComposer new] loadWithType:MMLoadTypeMore completionBlock:nil];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

步驟二:配置化

配置文件其實就是把測試工程總的接口調用代碼拷貝一份到單獨的配置文件中,配置文件如下

    [[GameDetailDataComposer new] loadDataWithBlock:nil];
    
    [[PubSearchDataComposer new] loadSuggestionWithCompletionBlock:nil];
    
    [[WriterDataComposer new] loadWithType:MMLoadTypeMore completionBlock:nil];

步驟三:自動目標文件的處理

這個是最核心的部分,主要包含了以下內容:

  • 配置文件路徑配置和需要注入的源碼文件夾的配置
  • 讀取配置文件的注入內容
  • 讀取源碼文件夾下的源碼實現文件(XXX.m)
  • 把注入內容添加到源碼中指定的位置
  • 從源碼從把注入的內容刪除

完整的腳本如下,裏面有比較完整的註釋,閱讀起來應該不會有太大難度:

文本的插入和刪除部分使用的是shell中的sed(stream editor)工具,特別滴在mac中sed命令和標準的sed命令有差別,腳本中也會有做這部分的檢測,如果機器上安裝的不是標準的gun sed程序會自動通過brew安裝gun sed

#!/bin/bash

############## 配置

# 需處理文件目錄
# mark: TODO
to_process_file_dir="$(pwd)/../injectedContentKit/Business000"
# 配置文件
cfg_file="$(pwd)/injectedContentConfig.cfg"

############## 工具類方法
function printHighlightMessage {
	echo -e "\033[31m $1 \033[0m"
}


# 檢查是否安裝gunsed
# mac安裝gunSed  http://blog.csdn.net/sun_wangdong/article/details/71078083
which_sed=`which sed`
echo $which_sed
echo "testresult = $(expr $which_sed : '.*/gnu-sed/')"
if [[ $(expr $which_sed : '.*/gnu-sed/') -gt 0 ]]; then
	echo "檢測到使用gun sed"
else
	if [ ! `which brew` ]
	then
		echo 'Homebrew not found. Trying to install...'
                    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" \
			|| exit 1
	fi
	echo 'Trying to install gun sed...'
	brew install gnu-sed --with-default-names || exit 1
	# 設置局部環境變量
	echo "set PATH...."
	source ./set-gun-sed-path.sh
	echo "set PATH done"

	#mark: echo 顏色選項 http://www.jb51.net/article/43968.htm
	echo "請手動執行命令,然後重新執行"
	command="PATH=\"/usr/local/Cellar/gnu-sed/4.4/bin:\$PATH\""
	printHighlightMessage $command
	echo ""
	exit 1
fi


# 循環檢測輸入的文件夾
function checkInputDestDir {
	echo -n "請輸入需處理源碼目錄: "
	read path
	if [[ -d $path ]]; then
		to_process_file_dir=$path
	else
		echo -n "輸入的目錄無效,"
		checkInputDestDir
	fi
}

# 需處理源碼目錄檢查
if [[ -d $to_process_file_dir ]]; then
	echo "需處理源碼目錄存在 $to_process_file_dir"
else
	echo "請確認需處理源碼目錄是否存在 $to_process_file_dir"
	checkInputDestDir
fi

# mark: p261
# 配置文件檢查
if [[ -f $cfg_file ]]; then
	echo "檢測到配置文件存在 $cfg_file"
else
	echo "請確認配置文件是否存在 $cfg_file"
	exit 1
fi

# 讀取配置文件
echo "開始讀取配置文件..."

declare -a config_content_array
cfg_line_count=0
# 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
	if [[ ${#line} -eq 0 ]]; then
		echo "blank line"
	else
		config_content_array[$cfg_line_count]=$line
	fi
	cfg_line_count=$[ $cfg_line_count + 1 ]
done
IFS=${IFS_OLD}


echo ""

# 讀取需要處理目標文件
declare -a implement_source_file_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}
					implement_source_file_count=$[ implement_source_file_count + 1 ];
				fi
				echo ""
			fi
		done
	else
		echo "err:不是一個目錄"
	fi
}


echo ${to_process_file_dir}
read_implement_file_recursively ${to_process_file_dir}


# 處理目標文件,添加配置文件中注入的內容
function addInjectedContent {
	# implement_source_file_array
	# ${#config_content_array[@]}
	injected_content_index=0
	for(( i=0;i<${#implement_source_file_array[@]};i++)) 
	do 
		file=${implement_source_file_array[i]}; 
		echo ${file}
		injected_content=${config_content_array[$injected_content_index]};
		injected_content_index=$[ $injected_content_index + 1 ]

		echo ">>>>>>>${injected_content}"
		# mark: sed 命令中使用變量 http://blog.csdn.net/lepton126/article/details/36374933
		sed -i '/^- \(.*\)/{
			a\ '"$injected_content"'
		}' ${file}

	done;
	message="內容添加完成"
	printHighlightMessage $message
}


# 處理目標文件,刪除配置文件中注入的內容
function removeInjectedContent {
	for(( i=0;i<${#implement_source_file_array[@]};i++)) 
	do 
		file=${implement_source_file_array[i]}; 
		echo ${file}

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

			# mark: sed 命令中使用變量 http://blog.csdn.net/lepton126/article/details/36374933
			substring="["
			replacement="\["
			pattern_str=${pattern_str//$substring/$replacement}
			substring="]"
			replacement="\]"
			pattern_str=${pattern_str//$substring/$replacement}
			echo "pattern_str = $pattern_str"
			#pattern_str="[CardDataComposer new]"

			sed -i '/'"$pattern_str"'/ {
				d
			}' ${file}
		done
	done;
	message="內容刪除完成"
	printHighlightMessage $message
}


function genMunu {
	clear
	echo
	echo -e "\t\t\t選項菜單\n"
	echo -e "\t1. 刪除注入內容"
	echo -e "\t2. 添加註入內容"
	echo -e "\t0. Exit menu\n\n"
	echo -en "\t\tEnter option: "
	read -n 1 option
}


while [[ 1 ]]; do
	genMunu
	case $option in
	0 )
		echo ""
		echo "Bye"
		exit 0
	;;
	1 )
		# 刪除配置文件中注入的內容
		removeInjectedContent
	;;
	2 )
		# 添加配置文件中注入的內容
		addInjectedContent
	;;
	h )
		genMunu
	;;
	* )
		echo "Wrong!!"
	;;
	esac

	echo
	echo -en "\n\n\tHit any key to continue"
	read -n 1 line

done

總結

以上就是基於shell腳本,從混淆內容注入和把混淆內容刪除兩方面做了一個半自動化的實現步驟,如果不妥之處,還請不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章