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

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

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

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")


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重签名与测试
相关文章
|
3月前
|
IDE 网络安全 开发工具
【Azure App Service】Local Git App Service的仓库代码遇见卡住不Clone代码的问题
【Azure App Service】Local Git App Service的仓库代码遇见卡住不Clone代码的问题
【Azure App Service】Local Git App Service的仓库代码遇见卡住不Clone代码的问题
|
4天前
|
JavaScript C++ 容器
【Azure Bot Service】部署NodeJS ChatBot代码到App Service中无法自动启动
2024-11-12T12:22:40.366223350Z Error: Cannot find module 'dotenv' 2024-11-12T12:40:12.538120729Z Error: Cannot find module 'restify' 2024-11-12T12:48:13.348529900Z Error: Cannot find module 'lodash'
28 11
|
18天前
|
机器人 Shell Linux
【Azure Bot Service】部署Python ChatBot代码到App Service中
本文介绍了使用Python编写的ChatBot在部署到Azure App Service时遇到的问题及解决方案。主要问题是应用启动失败,错误信息为“Failed to find attribute 'app' in 'app'”。解决步骤包括:1) 修改`app.py`文件,添加`init_func`函数;2) 配置`config.py`,添加与Azure Bot Service认证相关的配置项;3) 设置App Service的启动命令为`python3 -m aiohttp.web -H 0.0.0.0 -P 8000 app:init_func`。
|
3月前
|
缓存
【Azure Function】Function App代码中使用Managed Identity认证获取Blob数据时遇见400报错
【Azure Function】Function App代码中使用Managed Identity认证获取Blob数据时遇见400报错
【Azure Function】Function App代码中使用Managed Identity认证获取Blob数据时遇见400报错
|
3月前
|
存储 Linux 网络安全
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)
|
3月前
|
网络安全 API 数据安全/隐私保护
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)
|
3月前
|
Linux Docker 容器
【Azure 应用服务】使用App Service for Linux/Container时,如果代码或Container启动耗时大于了230秒,默认会启动失败。
【Azure 应用服务】使用App Service for Linux/Container时,如果代码或Container启动耗时大于了230秒,默认会启动失败。
|
3月前
|
开发框架 安全 前端开发
【Azure 应用服务】应用代码需要客户端证书进行验证,部署到App Service后,如何配置让客户端携带证书呢?
【Azure 应用服务】应用代码需要客户端证书进行验证,部署到App Service后,如何配置让客户端携带证书呢?
|
3月前
|
开发框架 JSON .NET
【Azure 应用服务】在Azure App Service多实例的情况下,如何在应用中通过代码获取到实例名(Instance ID)呢?
【Azure 应用服务】在Azure App Service多实例的情况下,如何在应用中通过代码获取到实例名(Instance ID)呢?
|
3月前
【Azure 应用程序见解】通过无代码方式在App Service中启用Application Insights后,如何修改在Application Insights中显示的App Service实例名呢?
【Azure 应用程序见解】通过无代码方式在App Service中启用Application Insights后,如何修改在Application Insights中显示的App Service实例名呢?