目前公司產(chǎn)品線中存在大量功能類似的APP,按照模塊化方式開發(fā)項目,核心模塊業(yè)務(wù)代碼是復(fù)用的,使用同一個開發(fā)者賬號下iOS上架流程中有些APP在蘋果機審過程中慘遭被拒的下場,通過更改部分頁面UI效果也無濟于事,于是采用代碼混淆的方式也就是馬甲包方案去繞過機審;
功能分析
- 二進制不同,圖標,包名,工程名,代碼,靜態(tài)資源等的修改。
- 差異化UI風(fēng)格,產(chǎn)品功能,頁面布局等的修改
實現(xiàn)流程
- 核心模塊類名修改
- 核心方法名修改
- 加入垃圾代碼
- 替換png等靜態(tài)資源MD5
- info.plist文件添加垃圾字段

類名修改
- 遍歷查找需要替換的核心模塊目錄 (主工程\Pods目錄)
- 找到所有需要替換的類名(項目專用前綴),將其存放到數(shù)組中
- 遍歷查找整個工程的所有目錄,查找所有.h、.m、.xib、.string文件,逐行掃描文件,找到需要替換的類名關(guān)鍵字替換成別的名字前綴
- 如發(fā)現(xiàn).h、.m、.xib、.string文件的文件名包含需要替換的類名,替換之(xcodeproj工程需要重新引入文件,通過腳本動態(tài)引入)
- 遇到有"+"號的分類文件,篩選出"+"號前面的類名然后替換之
#遍歷查找所有.h、.m、.xib、.strings文件,逐行掃描文件,找到需要替換的類名關(guān)鍵字替換成別的名字前綴
def do_replace_file_name(dir_path,need_name_list)
Dir.foreach(dir_path) do |file|
if file != "." and file != ".."
file_path = dir_path + "/" + file
if File.directory? file_path
do_replace_file_name(file_path,need_name_list)
else
if file.end_with?(".h") or file.end_with?(".m") or file.end_with?(".xib") or file.end_with?(".strings") #只查找.h .m .xib .strings文件批量修改
aFile = File.new(file_path, "r")
if aFile
file_content = aFile.read()
aFile.close
length = need_name_list.length - 1
for i in 0..length do
need_name = need_name_list[i]
file_content = split_file_content(file_content,need_name)
end
aFile = File.new(file_path, "w")
aFile.syswrite(file_content)
aFile.rewind
end
end
#如.h、.m、.xib、.string文件的文件名包含需要替換的類名,替換之
if file.include?".h" or file.include?".m" or file.include?".xib" or file.include?".strings"
file_suffix = file.split(".")[1]
need_name_list.each { |need_name|
need_file_name = need_name + "." + file_suffix
if file.start_with?(need_file_name)
new_file_name = new_file_name(file)
new_file_path = dir_path + "/" + new_file_name
File.rename(file_path, new_file_path) #文件名稱替換
end
}
end
end
end
end
end
方法名修改
- 獲取系統(tǒng)文件關(guān)鍵字并緩存,主要是獲取iOS SDK中Frameworks所有方法名和參數(shù)名作為忽略關(guān)鍵字
- 遍歷查找整個工程的所有.h、.m、.mm文件,提取關(guān)鍵字,主要提取方法名和參數(shù)名
- 將系統(tǒng)關(guān)鍵字、IBAction方法的關(guān)鍵字、屬性property的關(guān)鍵字(防止懶加載方法名造成沖突)去除
- 將剩余的關(guān)鍵字進行方法混淆,混淆方案是將名字用#define宏定義方式替換名稱,方法不能替換成隨機字符串,這樣任然不能通過機審,應(yīng)替換成規(guī)律的單詞拼接方法名
- 將替換后方法名關(guān)鍵字宏名稱寫入到全局pch文件,xcodeproj動態(tài)引入
# 生成混淆文件
@staticmethod
def create_confuse_file(output_file, confused_dict):
log_info("Start creating confuse file, file fullpath is {0}".format(os.path.realpath(output_file)), 2, True)
f = open(output_file, 'wb')
f.write(bytes('#ifndef NEED_CONFUSE_h\n', encoding='utf-8'))
f.write(bytes('#define NEED_CONFUSE_h\n', encoding='utf-8'))
f.write(bytes('// 生成時間: {0}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')), encoding='utf-8'))
for (key, value) in confused_dict.items():
f.write(bytes('#define {0} {1}\n'.format(key, value), encoding='utf-8'))
f.write(bytes('#endif', encoding='utf-8'))
f.close()
生成垃圾代碼
- 遍歷查找整個工程的所有.m、.mm文件
- 為避免和混淆后的方法重名,添加垃圾方法的時候使用 隨機前綴 + "_" + 規(guī)律單詞 作為方法名,隨意在方法中添加日志代碼
- 在文件結(jié)尾@end前插入這些方法
#oc代碼以@end結(jié)尾,在其前面添加text
def appendTextToOCFile(file_path, text):
with open(file_path, "r") as fileObj:
old_text = fileObj.read()
fileObj.close()
end_mark_index = old_text.rfind("@end")
if end_mark_index == -1:
print "\t非法的結(jié)尾格式: " + file_path
return
new_text = old_text[:end_mark_index]
new_text = new_text + text + "\n"
new_text = new_text + old_text[end_mark_index:]
with open(file_path, "w") as fileObj:
fileObj.write(new_text)
#處理單個OC文件,添加垃圾函數(shù)。確保其對應(yīng)頭文件存在于相同目錄
def dealWithOCFile(filename, file_path):
global target_ios_folder,create_func_min,create_func_max,funcname_set
funcname_set.clear()
end_index = file_path.rfind(".")
pre_name = file_path[:end_index]
header_path = pre_name + ".h"
if not os.path.exists(header_path):
print "\t相應(yīng)頭文件不存在:" + file_path
return
new_func_num = random.randint(create_func_min, create_func_max)
print "\t給%s添加%d個方法" %(filename, new_func_num)
prefix_list = ['btt_', 'gym_', 'muut_', 'ora_', 'vend_', 'enyt_', 'qotb_', 'ldt_', 'zndy_', 'tim_', 'yar_', 'toa_', 'rewwy_', 'twof_', 'theg_', 'guis_', 'dui_' ]
random_index_list = random.sample(range(0,new_func_num), new_func_num)
for i in range(new_func_num):
prefix = prefix_list[random_index_list[i]]
header_text = getOCHeaderFuncText(prefix)
# print "add %s to %s" %(header_text, header_path.replace(target_ios_folder, ""))
appendTextToOCFile(header_path, header_text + ";\n")
funcText = getOCFuncText(header_text)
appendTextToOCFile(file_path, funcText)
替換png等靜態(tài)資源MD5
if file_type == ".png":
text = "".join(random.sample(string.ascii_letters, 11))
elif file_type == ".jpg":
text = "".join(random.sample(string.ascii_letters, 20))
elif file_type == ".lua":
text = "\n--#*" + "".join(random.sample(string.ascii_letters, 10)) + "*#--"
else:
text = " "*random.randint(1, 100)
fileObj.write(text)
fileObj.close()
info.plist文件添加垃圾字段
在info.plist中插入規(guī)律英文單詞(已排除系統(tǒng)專用字段),值為隨機字符串
def addPlistField(plist_file):
global create_field_min,create_field_max,word_name_list
create_field_num = random.randint(create_field_min, create_field_max)
random_index_list = random.sample(word_name_list, create_field_num)
tree = ET.parse(plist_file)
root = tree.getroot()
root_dict = root.find("dict")
for i in range(create_field_num):
key_node = ET.SubElement(root_dict,"key")
key_node.text = random_index_list[i]
string_node = ET.SubElement(root_dict,"string")
string_node.text = getOneName()
tree.write(plist_file,"UTF-8")
混淆前后對比
代碼混淆前

Hopper查看混淆前

代碼混淆后

Hopper查看混淆后
