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

目录

iOS代码混淆

功能分析

实现流程

类名修改

方法名修改

生成垃圾代码

替换png等静态资源MD5

info.plist文件添加垃圾字段

功能分析

实现流程

类名修改

方法名修改

生成垃圾代码

替换png等静态资源MD5

info.plist文件添加垃圾字段

混淆前后对比


iOS代码混淆

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

功能分析

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

实现流程

  • 核心模块类名修改

  • 核心方法名修改

  • 加入垃圾代码

  • 替换png等静态资源MD5

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

类名修改

  • 遍历查找需要替换的核心模块目录 (主工程\Pods目录)

  • 找到所有需要替换的类名(项目专用前缀),将其存放到数组中

  • 遍历查找整个工程的所有目录,查找所有.h、.m、.xib、.string文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀

  • 如发现.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之(xcodeproj工程需要重新引入文件,通过脚本动态引入)

  • 遇到有"+"号的分类文件,筛选出"+"号前面的类名然后替换之

    applescript复制代码#遍历查找所有.h、.m、.xib、.strings文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    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

方法名修改

  • 获取系统文件关键字并缓存,主要是获取iOS SDK中Frameworks所有方法名和参数名作为忽略关键字

  • 遍历查找整个工程的所有.h、.m、.mm文件,提取关键字,主要提取方法名和参数名

  • 将系统关键字、IBAction方法的关键字、属性property的关键字(防止懒加载方法名造成冲突)去除

  • 将剩余的关键字进行方法混淆,混淆方案是将名字用#define宏定义方式替换名称,方法不能替换成随机字符串,这样任然不能通过机审,应替换成规律的单词拼接方法名

  • 将替换后方法名关键字宏名称写入到全局pch文件,xcodeproj动态引入

    pgsql复制代码 # 生成混淆文件
    @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文件

  • 为避免和混淆后的方法重名,添加垃圾方法的时候使用 随机前缀 + "_" + 规律单词 作为方法名,随意在方法中添加日志代码

  • 在文件结尾@end前插入这些方法

    haxe复制代码#oc代码以@end结尾,在其前面添加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非法的结尾格式: " + 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文件,添加垃圾函数。确保其对应头文件存在于相同目录
    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相应头文件不存在:" + 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等静态资源MD5

复制代码
livecodeserver复制代码        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中插入规律英文单词(已排除系统专用字段),值为随机字符串

复制代码
scss复制代码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")

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

功能分析

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

实现流程

  • 核心模块类名修改

  • 核心方法名修改

  • 加入垃圾代码

  • 替换png等静态资源MD5

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

类名修改

  • 遍历查找需要替换的核心模块目录 (主工程\Pods目录)

  • 找到所有需要替换的类名(项目专用前缀),将其存放到数组中

  • 遍历查找整个工程的所有目录,查找所有.h、.m、.xib、.string文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀

  • 如发现.h、.m、.xib、.string文件的文件名包含需要替换的类名,替换之(xcodeproj工程需要重新引入文件,通过脚本动态引入)

  • 遇到有"+"号的分类文件,筛选出"+"号前面的类名然后替换之

    applescript复制代码#遍历查找所有.h、.m、.xib、.strings文件,逐行扫描文件,找到需要替换的类名关键字替换成别的名字前缀
    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

方法名修改

  • 获取系统文件关键字并缓存,主要是获取iOS SDK中Frameworks所有方法名和参数名作为忽略关键字

  • 遍历查找整个工程的所有.h、.m、.mm文件,提取关键字,主要提取方法名和参数名

  • 将系统关键字、IBAction方法的关键字、属性property的关键字(防止懒加载方法名造成冲突)去除

  • 将剩余的关键字进行方法混淆,混淆方案是将名字用#define宏定义方式替换名称,方法不能替换成随机字符串,这样任然不能通过机审,应替换成规律的单词拼接方法名

  • 将替换后方法名关键字宏名称写入到全局pch文件,xcodeproj动态引入

    pgsql复制代码 # 生成混淆文件
    @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文件

  • 为避免和混淆后的方法重名,添加垃圾方法的时候使用 随机前缀 + "_" + 规律单词 作为方法名,随意在方法中添加日志代码

  • 在文件结尾@end前插入这些方法

    haxe复制代码#oc代码以@end结尾,在其前面添加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非法的结尾格式: " + 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文件,添加垃圾函数。确保其对应头文件存在于相同目录
    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相应头文件不存在:" + 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等静态资源MD5

复制代码
livecodeserver复制代码        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中插入规律英文单词(已排除系统专用字段),值为随机字符串

复制代码
scss复制代码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查看混淆后

假如你不知道如何代码混淆和如何创建文件混淆,你可以参考下面这个教程来使用我们平台代码混淆和文件混淆以及重签名:怎么保护苹果手机移动应用程序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重签名与测试
相关推荐
jian1105815 小时前
Mac 如何找到快捷键截屏被哪个程序设置使用的,
macos
水月天涯16 小时前
Mac系统下制作 Ubuntu镜像(小白教程)
linux·ubuntu·macos
习惯就好zz16 小时前
记一次 Mac SSH 免密登录 Windows 的踩坑与修复
windows·macos·ssh
utmhikari17 小时前
【DIY小记】解决MacOS上Edge浏览器bilibili全屏卡顿的问题
前端·macos·性能优化·edge·bilibili
2501_9151063217 小时前
不依赖 Xcode 的 iOS 编译器,kxapp 中 kxbuild 工具详解
ide·vscode·ios·cocoa·个人开发·xcode·敏捷流程
派带星-18 小时前
基于 C++ 的第三方 SDK 封装实践(ASR + 短信服务)
c++·ide·macos
long_songs18 小时前
Python编程第02课:Windows/Mac/Linux环境安装配置详解
windows·python·macos
Lucas_coding18 小时前
【语音相关】Opus编码器生命周期管理:从“有噪音“到“无噪音“的完美转换 [opus, pcm 转化电流音问题解决]
macos·xcode·pcm
最贪吃的虎18 小时前
Mac安装Git教程
git·macos
2501_916008892 天前
iOS开发者工具有哪些?Xcode、Fastlane 与 kxapp 的组合使用
ide·vscode·macos·ios·个人开发·xcode·敏捷流程