背景
公司需要做一系列的殼版本,殼版本如果內容雷同提交到App Store會有被拒絕的風險,其中有一種解決方案是在殼版本中注入混淆的程式碼,防止被蘋果檢測到內容太過雷同而導致稽核被拒絕,本文是針對這個場景,使用shell指令碼進行半自動批量新增和刪除混淆程式碼。
結果
下面以兩張圖作為新增註入內容和刪除注入內容的演示:
新增註入內容到原始碼中
把注入內容從原始碼中刪除
本文的Demo程式碼YTTInjectedContentKit
[圖片上傳失敗...(image-9cb239-1521607769520)]
分析
在開始做之前,對步驟流程做了一些構思如下:
初始步驟流程
-
步驟一:手動處理
混淆注入類列表
混淆注入類對應的方法列表 -
步驟二:配置化
步驟一的形成配置化 -
步驟三:自動化
掃描對應的類和類對應的方法列表,形成對應的配置檔案
從配置檔案中讀取配置注入到對應的目標類中 -
步驟四:自動目標檔案的處理
目標檔案的查詢規則:哪些是需要注入的目標檔案
目標檔案的注入規則:目標檔案中需要在什麼位置進行注入 -
步驟五:注入內容自身配置
注入內容需要在不同環境下變換不同的形態,包括類名,方法名等
後面在實現的過程中發現步驟三和步驟五不好實現,所有簡化了這部分的流程,最終只保留了以下幾個步驟:
優化的步驟流程
-
步驟一:手動處理
混淆注入類列表
混淆注入類對應的方法列表 -
步驟二:配置化
步驟一的形成配置化 -
步驟三:自動目標檔案的處理
目標檔案的查詢規則:哪些是需要注入的目標檔案
目標檔案的注入規則:目標檔案中需要在什麼位置進行注入
以及在實現過程中遇到了一些細節需要處理,這些細節部分作為自步驟如下:
- 子步驟:
步驟三-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指令碼,從混淆內容注入和把混淆內容刪除兩方面做了一個半自動化的實現步驟,如果不妥之處,還請不吝賜教。