• 如何通过代码混淆绕过苹果机审,解决APP被拒问题


    如何通过代码混淆绕过苹果机审,解决APP被拒问题

    目录

    iOS代码混淆

    功能分析

    实现流程

    类名修改

    方法名修改

    生成垃圾代码

    替换png等静态资源MD5

    info.plist文件添加垃圾字段

    功能分析

    实现流程

    类名修改

    方法名修改

    生成垃圾代码

    替换png等静态资源MD5

    info.plist文件添加垃圾字段

    混淆前后对比


    iOS代码混淆

    目前公司产品线中存在大量功能类似的APP,按照模块化方式开发项目,核心模块业务代码是复用的,使用同一个开发者账号下iOS上架流程中有些APP在苹果机审过程中惨遭被拒的下场,通过更改部分页面UI效果也无济于事,于是采用代码混淆的方式也就是马甲包方案去绕过机审;

    功能分析

    • 二进制不同,图标,包名,工程名,代码,静态资源等的修改。
    • 差异化UI风格,产品功能,页面布局等的修改

    实现流程

    • 核心模块类名修改

    • 核心方法名修改

    • 加入垃圾代码

    • 替换png等静态资源MD5

    • info.plist文件添加垃圾字段

      image.png

    类名修改

    • 遍历查找需要替换的核心模块目录 (主工程\Pods目录)
    • 找到所有需要替换的类名(项目专用前缀),将其存放到数组中
    • 遍历查找整个工程的所有目录,查找所有.h、.m、.xib、.string文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    • 如发现.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之(xcodeproj工程需要重新引入文件,通过脚本动态引入)
    • 遇到有"+"号的分类文件,筛选出"+"号前面的类名然后替换之
    1. applescript复制代码#遍历查找所有.h、.m、.xib、.strings文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    2. def do_replace_file_name(dir_path,need_name_list)
    3. Dir.foreach(dir_path) do |file|
    4. if file != "." and file != ".."
    5. file_path = dir_path + "/" + file
    6. if File.directory? file_path
    7. do_replace_file_name(file_path,need_name_list)
    8. else
    9. if file.end_with?(".h") or file.end_with?(".m") or file.end_with?(".xib") or file.end_with?(".strings") #只查找.h .m .xib .strings文件批量修改
    10. aFile = File.new(file_path, "r")
    11. if aFile
    12. file_content = aFile.read()
    13. aFile.close
    14. length = need_name_list.length - 1
    15. for i in 0..length do
    16. need_name = need_name_list[i]
    17. file_content = split_file_content(file_content,need_name)
    18. end
    19. aFile = File.new(file_path, "w")
    20. aFile.syswrite(file_content)
    21. aFile.rewind
    22. end
    23. end
    24. #如.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之
    25. if file.include?".h" or file.include?".m" or file.include?".xib" or file.include?".strings"
    26. file_suffix = file.split(".")[1]
    27. need_name_list.each { |need_name|
    28. need_file_name = need_name + "." + file_suffix
    29. if file.start_with?(need_file_name)
    30. new_file_name = new_file_name(file)
    31. new_file_path = dir_path + "/" + new_file_name
    32. File.rename(file_path, new_file_path) #文件名称替换
    33. end
    34. }
    35. end
    36. end
    37. end
    38. end
    39. end

    方法名修改

    • 获取系统文件关键字并缓存,主要是获取iOS SDK中Frameworks所有方法名和参数名作为忽略关键字
    • 遍历查找整个工程的所有.h、.m、.mm文件,提取关键字,主要提取方法名和参数名
    • 将系统关键字、IBAction方法的关键字、属性property的关键字(防止懒加载方法名造成冲突)去除
    • 将剩余的关键字进行方法混淆,混淆方案是将名字用#define宏定义方式替换名称,方法不能替换成随机字符串,这样任然不能通过机审,应替换成规律的单词拼接方法名
    • 将替换后方法名关键字宏名称写入到全局pch文件,xcodeproj动态引入
    1. pgsql复制代码 # 生成混淆文件
    2. @staticmethod
    3. def create_confuse_file(output_file, confused_dict):
    4. log_info("Start creating confuse file, file fullpath is {0}".format(os.path.realpath(output_file)), 2, True)
    5. f = open(output_file, 'wb')
    6. f.write(bytes('#ifndef NEED_CONFUSE_h\n', encoding='utf-8'))
    7. f.write(bytes('#define NEED_CONFUSE_h\n', encoding='utf-8'))
    8. f.write(bytes('// 生成时间: {0}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')), encoding='utf-8'))
    9. for (key, value) in confused_dict.items():
    10. f.write(bytes('#define {0} {1}\n'.format(key, value), encoding='utf-8'))
    11. f.write(bytes('#endif', encoding='utf-8'))
    12. f.close()

    生成垃圾代码

    • 遍历查找整个工程的所有.m、.mm文件
    • 为避免和混淆后的方法重名,添加垃圾方法的时候使用 随机前缀 + "_" + 规律单词 作为方法名,随意在方法中添加日志代码
    • 在文件结尾@end前插入这些方法
    1. haxe复制代码#oc代码以@end结尾,在其前面添加text
    2. def appendTextToOCFile(file_path, text):
    3. with open(file_path, "r") as fileObj:
    4. old_text = fileObj.read()
    5. fileObj.close()
    6. end_mark_index = old_text.rfind("@end")
    7. if end_mark_index == -1:
    8. print "\t非法的结尾格式: " + file_path
    9. return
    10. new_text = old_text[:end_mark_index]
    11. new_text = new_text + text + "\n"
    12. new_text = new_text + old_text[end_mark_index:]
    13. with open(file_path, "w") as fileObj:
    14. fileObj.write(new_text)
    15. #处理单个OC文件,添加垃圾函数。确保其对应头文件存在于相同目录
    16. def dealWithOCFile(filename, file_path):
    17. global target_ios_folder,create_func_min,create_func_max,funcname_set
    18. funcname_set.clear()
    19. end_index = file_path.rfind(".")
    20. pre_name = file_path[:end_index]
    21. header_path = pre_name + ".h"
    22. if not os.path.exists(header_path):
    23. print "\t相应头文件不存在:" + file_path
    24. return
    25. new_func_num = random.randint(create_func_min, create_func_max)
    26. print "\t给%s添加%d个方法" %(filename, new_func_num)
    27. prefix_list = ['btt_', 'gym_', 'muut_', 'ora_', 'vend_', 'enyt_', 'qotb_', 'ldt_', 'zndy_', 'tim_', 'yar_', 'toa_', 'rewwy_', 'twof_', 'theg_', 'guis_', 'dui_' ]
    28. random_index_list = random.sample(range(0,new_func_num), new_func_num)
    29. for i in range(new_func_num):
    30. prefix = prefix_list[random_index_list[i]]
    31. header_text = getOCHeaderFuncText(prefix)
    32. # print "add %s to %s" %(header_text, header_path.replace(target_ios_folder, ""))
    33. appendTextToOCFile(header_path, header_text + ";\n")
    34. funcText = getOCFuncText(header_text)
    35. appendTextToOCFile(file_path, funcText)

    替换png等静态资源MD5

    1. livecodeserver复制代码 if file_type == ".png":
    2. text = "".join(random.sample(string.ascii_letters, 11))
    3. elif file_type == ".jpg":
    4. text = "".join(random.sample(string.ascii_letters, 20))
    5. elif file_type == ".lua":
    6. text = "\n--#*" + "".join(random.sample(string.ascii_letters, 10)) + "*#--"
    7. else:
    8. text = " "*random.randint(1, 100)
    9. fileObj.write(text)
    10. fileObj.close()

    info.plist文件添加垃圾字段

    在info.plist中插入规律英文单词(已排除系统专用字段),值为随机字符串

    1. scss复制代码def addPlistField(plist_file):
    2. global create_field_min,create_field_max,word_name_list
    3. create_field_num = random.randint(create_field_min, create_field_max)
    4. random_index_list = random.sample(word_name_list, create_field_num)
    5. tree = ET.parse(plist_file)
    6. root = tree.getroot()
    7. root_dict = root.find("dict")
    8. for i in range(create_field_num):
    9. key_node = ET.SubElement(root_dict,"key")
    10. key_node.text = random_index_list[i]
    11. string_node = ET.SubElement(root_dict,"string")
    12. string_node.text = getOneName()
    13. tree.write(plist_file,"UTF-8")

    目前公司产品线中存在大量功能类似的APP,按照模块化方式开发项目,核心模块业务代码是复用的,使用同一个开发者账号下iOS上架流程中有些APP在苹果机审过程中惨遭被拒的下场,通过更改部分页面UI效果也无济于事,于是采用代码混淆的方式也就是马甲包方案去绕过机审;

    功能分析

    • 二进制不同,图标,包名,工程名,代码,静态资源等的修改。
    • 差异化UI风格,产品功能,页面布局等的修改

    实现流程

    • 核心模块类名修改

    • 核心方法名修改

    • 加入垃圾代码

    • 替换png等静态资源MD5

    • info.plist文件添加垃圾字段

      image.png

    类名修改

    • 遍历查找需要替换的核心模块目录 (主工程\Pods目录)
    • 找到所有需要替换的类名(项目专用前缀),将其存放到数组中
    • 遍历查找整个工程的所有目录,查找所有.h、.m、.xib、.string文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    • 如发现.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之(xcodeproj工程需要重新引入文件,通过脚本动态引入)
    • 遇到有"+"号的分类文件,筛选出"+"号前面的类名然后替换之
    1. applescript复制代码#遍历查找所有.h、.m、.xib、.strings文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    2. def do_replace_file_name(dir_path,need_name_list)
    3. Dir.foreach(dir_path) do |file|
    4. if file != "." and file != ".."
    5. file_path = dir_path + "/" + file
    6. if File.directory? file_path
    7. do_replace_file_name(file_path,need_name_list)
    8. else
    9. if file.end_with?(".h") or file.end_with?(".m") or file.end_with?(".xib") or file.end_with?(".strings") #只查找.h .m .xib .strings文件批量修改
    10. aFile = File.new(file_path, "r")
    11. if aFile
    12. file_content = aFile.read()
    13. aFile.close
    14. length = need_name_list.length - 1
    15. for i in 0..length do
    16. need_name = need_name_list[i]
    17. file_content = split_file_content(file_content,need_name)
    18. end
    19. aFile = File.new(file_path, "w")
    20. aFile.syswrite(file_content)
    21. aFile.rewind
    22. end
    23. end
    24. #如.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之
    25. if file.include?".h" or file.include?".m" or file.include?".xib" or file.include?".strings"
    26. file_suffix = file.split(".")[1]
    27. need_name_list.each { |need_name|
    28. need_file_name = need_name + "." + file_suffix
    29. if file.start_with?(need_file_name)
    30. new_file_name = new_file_name(file)
    31. new_file_path = dir_path + "/" + new_file_name
    32. File.rename(file_path, new_file_path) #文件名称替换
    33. end
    34. }
    35. end
    36. end
    37. end
    38. end
    39. end

    方法名修改

    • 获取系统文件关键字并缓存,主要是获取iOS SDK中Frameworks所有方法名和参数名作为忽略关键字
    • 遍历查找整个工程的所有.h、.m、.mm文件,提取关键字,主要提取方法名和参数名
    • 将系统关键字、IBAction方法的关键字、属性property的关键字(防止懒加载方法名造成冲突)去除
    • 将剩余的关键字进行方法混淆,混淆方案是将名字用#define宏定义方式替换名称,方法不能替换成随机字符串,这样任然不能通过机审,应替换成规律的单词拼接方法名
    • 将替换后方法名关键字宏名称写入到全局pch文件,xcodeproj动态引入
    1. pgsql复制代码 # 生成混淆文件
    2. @staticmethod
    3. def create_confuse_file(output_file, confused_dict):
    4. log_info("Start creating confuse file, file fullpath is {0}".format(os.path.realpath(output_file)), 2, True)
    5. f = open(output_file, 'wb')
    6. f.write(bytes('#ifndef NEED_CONFUSE_h\n', encoding='utf-8'))
    7. f.write(bytes('#define NEED_CONFUSE_h\n', encoding='utf-8'))
    8. f.write(bytes('// 生成时间: {0}\n'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S')), encoding='utf-8'))
    9. for (key, value) in confused_dict.items():
    10. f.write(bytes('#define {0} {1}\n'.format(key, value), encoding='utf-8'))
    11. f.write(bytes('#endif', encoding='utf-8'))
    12. f.close()

    生成垃圾代码

    • 遍历查找整个工程的所有.m、.mm文件
    • 为避免和混淆后的方法重名,添加垃圾方法的时候使用 随机前缀 + "_" + 规律单词 作为方法名,随意在方法中添加日志代码
    • 在文件结尾@end前插入这些方法
    1. haxe复制代码#oc代码以@end结尾,在其前面添加text
    2. def appendTextToOCFile(file_path, text):
    3. with open(file_path, "r") as fileObj:
    4. old_text = fileObj.read()
    5. fileObj.close()
    6. end_mark_index = old_text.rfind("@end")
    7. if end_mark_index == -1:
    8. print "\t非法的结尾格式: " + file_path
    9. return
    10. new_text = old_text[:end_mark_index]
    11. new_text = new_text + text + "\n"
    12. new_text = new_text + old_text[end_mark_index:]
    13. with open(file_path, "w") as fileObj:
    14. fileObj.write(new_text)
    15. #处理单个OC文件,添加垃圾函数。确保其对应头文件存在于相同目录
    16. def dealWithOCFile(filename, file_path):
    17. global target_ios_folder,create_func_min,create_func_max,funcname_set
    18. funcname_set.clear()
    19. end_index = file_path.rfind(".")
    20. pre_name = file_path[:end_index]
    21. header_path = pre_name + ".h"
    22. if not os.path.exists(header_path):
    23. print "\t相应头文件不存在:" + file_path
    24. return
    25. new_func_num = random.randint(create_func_min, create_func_max)
    26. print "\t给%s添加%d个方法" %(filename, new_func_num)
    27. prefix_list = ['btt_', 'gym_', 'muut_', 'ora_', 'vend_', 'enyt_', 'qotb_', 'ldt_', 'zndy_', 'tim_', 'yar_', 'toa_', 'rewwy_', 'twof_', 'theg_', 'guis_', 'dui_' ]
    28. random_index_list = random.sample(range(0,new_func_num), new_func_num)
    29. for i in range(new_func_num):
    30. prefix = prefix_list[random_index_list[i]]
    31. header_text = getOCHeaderFuncText(prefix)
    32. # print "add %s to %s" %(header_text, header_path.replace(target_ios_folder, ""))
    33. appendTextToOCFile(header_path, header_text + ";\n")
    34. funcText = getOCFuncText(header_text)
    35. appendTextToOCFile(file_path, funcText)

    替换png等静态资源MD5

    1. livecodeserver复制代码 if file_type == ".png":
    2. text = "".join(random.sample(string.ascii_letters, 11))
    3. elif file_type == ".jpg":
    4. text = "".join(random.sample(string.ascii_letters, 20))
    5. elif file_type == ".lua":
    6. text = "\n--#*" + "".join(random.sample(string.ascii_letters, 10)) + "*#--"
    7. else:
    8. text = " "*random.randint(1, 100)
    9. fileObj.write(text)
    10. fileObj.close()

    info.plist文件添加垃圾字段

    在info.plist中插入规律英文单词(已排除系统专用字段),值为随机字符串

    1. scss复制代码def addPlistField(plist_file):
    2. global create_field_min,create_field_max,word_name_list
    3. create_field_num = random.randint(create_field_min, create_field_max)
    4. random_index_list = random.sample(word_name_list, create_field_num)
    5. tree = ET.parse(plist_file)
    6. root = tree.getroot()
    7. root_dict = root.find("dict")
    8. for i in range(create_field_num):
    9. key_node = ET.SubElement(root_dict,"key")
    10. key_node.text = random_index_list[i]
    11. string_node = ET.SubElement(root_dict,"string")
    12. string_node.text = getOneName()
    13. tree.write(plist_file,"UTF-8")

    混淆前后对比

    代码混淆前

    img

    Hopper查看混淆前

    img

    代码混淆后

    img

    Hopper查看混淆后

    img

    假如你不知道如何代码混淆和如何创建文件混淆,你可以参考下面这个教程来使用我们平台代码混淆和文件混淆以及重签名:怎么保护苹果手机移动应用程序ios ipa中的代码 | ipaguard使用教程

    Ipa Guard是一款功能强大的ipa混淆工具,不需要ios app源码,直接对ipa文件进行混淆加密。可对IOS ipa 文件的代码,代码库,资源文件等进行混淆保护。 可以根据设置对函数名、变量名、类名等关键代码进行重命名和混淆处理,降低代码的可读性,增加ipa破解反编译难度。可以对图片,资源,配置等进行修改名称,修改md5。只要是ipa都可以,不限制OC,Swift,Flutter,React Native,H5类app。

    总结

    在移动互联网时代,代码混淆越来越受到开发者的重视。 iOS代码混淆可以提高难度,从而防止应用程序被盗用或反编译,保护开发者的权益。但是同时也带来了一些问题,例如混淆后的函数名可能会影响代码的可维护性。因此,在使用代码混淆时需要进行合理规划。

    参考资料

    1. IpaGuard文档 - 代码混淆
    2. iOS代码混淆方案
    3. iOS文件混淆方案
    4. iOS重签名与测试

  • 相关阅读:
    OmniOutliner 5 Pro for Mac(信息大纲记录工具)v5.12正式版 支持M1、M2
    SpringBoot2.7.3 动态数据数据源以及多数据源自动配置
    小乌龟,git使用教程,gitLab打版教程
    [错题]电路维修
    日本购物网站的网络乞丐功能
    Python编程技巧 – 使用字符串(Strings)
    Windows版 PostgreSQL 利用 pg_upgrade 进行大版升级操作
    MOSN 反向通道详解
    安卓玩机搞机----不用刷第三方官改固件即可享受“高级设置”的操作 ChiMi安装使用步骤
    AtCoder Beginner Contest 280 老年人复建赛
  • 原文地址:https://blog.csdn.net/qq_24694139/article/details/133876870