热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[iOS逆向13]代码混淆

背景经过逆向工程实践,可以发现静态分析在整个过程中是不可缺少的,而且静态分析工具生成的伪代码极大地提高了分析效率。想象一下如果没有静态分析,实现解除会员限制的过程:连接界面调试器R

背景

经过逆向工程实践,可以发现静态分析在整个过程中是不可缺少的,而且静态分析工具生成的伪代码极大地提高了分析效率。想象一下如果没有静态分析,实现解除会员限制的过程:连接界面调试器 Reveal,找到目标界面,获取按钮地址,打印按钮的响应事件,获取响应方法的 C 函数指针,连接 LLDB 给该函数打断点,但是该函数内有大量的分支语句,每个分支都要通过调试才能判断是不是确定会员权限的函数,分析“一天”后找到目标函数,编写 hook 代码。致命问题是,对部分反调试策略无法应对,因为不知道在哪打断点,如果一条一条地执行指令来寻找反调试入口,可以视为找不到。

如果缺少了界面调试工具和动态调试工具,但有静态分析的汇编代码和伪代码在,只会略微增加分析时间。如果有静态混淆策略,首先在大型项目中应用的可能性就不大,就算有,因为系统符号名是不能替换的,所以可以从系统类入手。比如某个行为会弹窗,就在代码中搜索 UIAlertAction 之类的字眼,缩小目标范围,然后利用 IDA Pro 给符号重命名为有意义的字符串,感觉这个功能就是给名称混淆准备的。

综上,增加静态分析的代码逻辑复杂度才是保护 App 的关键。那么有没有办法让反编译得到的代码难以阅读呢?这就要从二进制代码的源头入手——在编译过程中替换指令、增加无用控制流、使代码扁平化等。因此,需要改变编译器生成代码的过程,而 LLVM 架构为这个思路的实现提供了可能。

LLVM

编译器架构一般分三个模块,分别是前端、优化器和后端。

源代码在预处理过程中展开宏定义,导入头文件,处理条件编译指令等,然后开始分词。分词过程中识别单词或者符号的类型并记录位置,比如一个左括号被识别为 l_paren + 位置。然后按照语言规则构造语法树,同时检验了语法。遍历语法树,将各节点翻译为一种中间代码。以上是编译器前端的工作,最终输出中间代码。对于传统编译器,每种语言的编译器前端生成的中间代码格式都不相同。前端生成的中间代码作为优化器的输入,优化器从时间、空间上优化代码,然后输出给后端。后端根据目标 CPU 支持的指令集、寄存器等信息生成目标文件。

LLVM 编译器架构与前面传统的编译过程不同的是,每种语言的前端输出的都是同种类型的中间代码,这个中间代码像是一种汇编,但在函数声明上又有一些高级语言的样子。这样做的好处是,对 IR 优化的工作可以完全复用。LLVM 架构用 Clang 作为 C 和类 C 语言的前端;优化器由多个流程(Pass)组成,各个流程对中间表示(IR)也就是中间代码从不同方面进行优化。LLVM 架构中的 IR 即 Bitcode。

苹果从 Xcode 7 开始默认使用 LLVM 编译器架构。在 App 准备上传时,打包 Archive 版本会把 Bitcode 附加到可执行文件中,查看 Mach-O 文件可以看到多出一个 LLVM 段 __LLVM,__bitcode。 苹果服务器会对 Bitcode 进一步优化,然后从 Bitcode 重新生成目标文件,因此从 App Store 下载下来的可执行文件和上传时的可执行文件会不一样。一些比较旧的第三方库不支持 LLVM 的 Bitcode,整个项目必须用传统方式编译,这时下载下来的可执行文件代码段会和上传时的相同。

回到正题,代码混淆中的改变执行流程、添加垃圾指令等操作,就相当于对代码进行负优化。而各个 Pass 正是负责对中间代码的处理、优化,因此我们可以添加自定义 Pass 来实现代码混淆。

Pass Demo

编译配置

从 GitHub 下载 LLVM 的源码,在 lib/Transforms/ 目录下可以看到 Hello 文件夹,这是 LLVM 自带的 Pass 入门项目,先模仿一下它。在相同目录下新建一个 MyDemo 文件夹,里面添加一个 cpp 文件和 CMakeLists 文件。CMakeLists 文件中使用 add_llvm_loadable_module 函数;在 Transforms 目录下的 CMakeLists 中添加 add_subdirectory(MyDemo)。然后为了方便写代码、读源码,用 CMake 生成 Xcode 项目,cmake ../llvm -G "Xcode"。用 Xcode 打开后,即可找到:
《[iOS 逆向 13] 代码混淆》
然后设置 Xcode 的编译 Scheme 为 MyDemo 的 Target,这样就可以开始写代码了。

编写 Demo

Pass 基类中提供了一些虚函数,不同的子类通过实现这些函数来提供不同的功能,系统默认实现了一些子类。 Demo 中定义一个类继承 FunctionPass 就可以操作代码中的函数了,导入相应的头文件,Demo Pass 声明如下:
《[iOS 逆向 13] 代码混淆》
LLVM 用变量 ID 的引用来识别 Pass,可以赋任意值;MyPass 重载了 runOnFunction,这里只打印函数名。然后指定使用该 Pass 的命令行参数为 demo:
static RegisterPass X("demo", "use demo pass", false, false);
开始构建,会生成 MyLLVMDemo.dylib 文件,下面使用这个 dylib。写一段测试代码,包含 func 函数 和 main 函数,然后使用 clang -emit-llvm -c 生成 Bitcode 文件。然后在 Xcode Scheme 中选择 opt 并构建。可以在命令行中使用 opt,但使用 Xcode 可以调试,用 Xcode 给 opt 的 Scheme 添加以下参数: 《[iOS 逆向 13] 代码混淆》
如果添加 -help 参数,会打印出该 Pass 接收的参数;运行时会输出函数名 func 和 main,用 Xcode 打断点,可以调试 Pass 过程:
《[iOS 逆向 13] 代码混淆》
可以在 opt 的 CMakeLists 中添加 MyPassDemo 的依赖,这样修改 Pass 代码后可以只构建 opt 也能让最新代码生效。

Obfuscator-LLVM

该论文主要介绍了三个 Pass 用于混淆。

  • 指令替换:将操作符替换为一系列的指令,例如a = b ^ c 替换为a = (b & !c) | (!b & c)
  • 控制流扁平化:把 if-else 自上而下的结构替换为 switch 扁平的结构,使逻辑变混乱。
  • 添加无用控制流:在原函数入口导向无用代码块,额外经历一系列无用流程。

现在要让 Clang 编译程序时加载以上 Pass,需要重新构建 Clang。由于该论文中使用的是比较旧的 LLVM-4.0,我下载了 LLVM-7.0.1 用于移植实验,需要执行以下操作:

  • 复制旧项目 lib/Transforms/Obfuscation 目录到相同位置,里面是 Pass 代码;
  • 修改 lib/Transforms 目录下的 CMakeLists,添加子目录 Obfuscation;
  • 修改 lib/Transforms 目录下的 LLVMBuild.txt,添加 Obfuscation;
  • 修改 Transforms/IPO 目录下的 LLVMBuild.txt,添加 Obfuscation;
  • 修改 Transforms/IPO 目录下的 PassManagerBuilder.cpp,把旧项目中相同位置关于 Obfuscation 的代码复制过去。

在 Xcode Scheme 中选择 Clang 并构建。构建完成后,用其编译下面的测试程序:
《[iOS 逆向 13] 代码混淆》
先不添加参数,生成可执行文件后用 IDA Pro 打开,生成的伪代码如下:
《[iOS 逆向 13] 代码混淆》
然后添加编译参数:-mllvm -sub 启用指令替换;-mllvm -bcf 启用伪造无用流程;-mllvm -bcf_prob=100 启用伪造流程时,一个基本块被生成无用代码的概率为 100%。
./clang test.c -o test.o -mllvm -sub -mllvm -bcf -mllvm -bcf_prob=100
生成可执行文件后,同样用 IDA Pro 反编译生成伪代码。可以看到,仅 func 函数就非常冗长,逻辑也不明显,这种混淆方式的效果比较好,可以极大地增加静态分析的难度。
《[iOS 逆向 13] 代码混淆》
在替换 Xcode 编译器之前,需要加一个 Target,执行cmake -DLLVM_CREATE_XCODE_TOOLCHAIN=ON ../llvm -G "Xcode",这样 Xcode Scheme 中才有 install-xcode-toolchain,选中并构建。注意硬盘要有 40G 左右的空闲空间,因为最终生成的工具链为 14G,编译过程中的文件 25G。生成的工具链位于 /usr/local/Toolchains,将其移动到 /Library/Developer/ 目录。打开 Xcode – Preference – Components,选择刚刚生成的工具链。
《[iOS 逆向 13] 代码混淆》
此时打开 Xcode 也可以看到当前使用的工具链:
《[iOS 逆向 13] 代码混淆》
用新工具链编译 Xcode 项目,报错unknown argument: '-index-store-path',需要到项目 Build Settings 关闭 Index-While-Building,因为这个参数在开源的 LLVM 项目中没有实现。然后编辑项目的 Compiler Flags,添加编译参数。《[iOS 逆向 13] 代码混淆》
我在参数中设置对基本块伪造无用流程的概率为 40%,那么项目的核心功能代码有六成概率不会被混淆,比较危险。但如果将概率设为 100%,大量无用代码势必减慢程序运行速度,因此需要对不同源文件采用不同的编译参数。前面提到过用命令行手动构建、打包 App,编写 makefile 确实可以做到精准控制,但是很麻烦。幸好 Xcode 提供了这个功能,在项目 Build Phase 中可以对源文件指定编译参数。
《[iOS 逆向 13] 代码混淆》
这样就可以实现对核心代码高度混淆、对不重要的代码部分混淆或不混淆。还有一个问题,项目中使用的第三方静态库,我们没有库的源码,如何进行混淆?对于启用了 Bitcode 的第三方静态库,可以先用 ar -x 命令解压得到其中的目标文件,因为 Bitcode 是保存在 Mach-O 文件的 LLVM 段 bitcode 节中,使用 segedit 命令提取出 bc 文件,然后用自己编译的 Clang 给定混淆参数将所有 bc 文件编译打包成新的静态库。这样做的缺点是新静态库没有 Bitcode,导致主项目也必须关闭 Bitcode 功能。


推荐阅读
  • 20189216 2018-2019-2 《密码与安全新技术专题》第二次作业
    201892162018-2019-2《密码与安全新技术专题》第二次作业课程:《密码与安全新技术专题》班级:1892班姓名:鲍政李学号:20189216上课教师:谢四江上课日期: ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • GPT-3发布,动动手指就能自动生成代码的神器来了!
    近日,OpenAI发布了最新的NLP模型GPT-3,该模型在GitHub趋势榜上名列前茅。GPT-3使用的数据集容量达到45TB,参数个数高达1750亿,训练好的模型需要700G的硬盘空间来存储。一位开发者根据GPT-3模型上线了一个名为debuid的网站,用户只需用英语描述需求,前端代码就能自动生成。这个神奇的功能让许多程序员感到惊讶。去年,OpenAI在与世界冠军OG战队的表演赛中展示了他们的强化学习模型,在限定条件下以2:0完胜人类冠军。 ... [详细]
  • 在开发中,有时候一个业务上要求的原子操作不仅仅包括数据库,还可能涉及外部接口或者消息队列。此时,传统的数据库事务无法满足需求。本文介绍了Java中如何利用java.lang.Runtime.addShutdownHook方法来保证业务线程的完整性。通过添加钩子,在程序退出时触发钩子,可以执行一些操作,如循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序。例子程序展示了如何利用钩子来保证业务线程的完整性。 ... [详细]
  • 本博文基于《Amalgamationofproteinsequence,structureandtextualinformationforimprovingprote ... [详细]
  • 语义分割系列3SegNet(pytorch实现)
    SegNet手稿最早是在2015年12月投出,和FCN属于同时期作品。稍晚于FCN,既然属于后来者,又是与FCN同属于语义分割网络 ... [详细]
  • 人工智能推理能力与假设检验
    最近Google的Deepmind开始研究如何让AI做数学题。这个问题的提出非常有启发,逻辑推理,发现新知识的能力应该是强人工智能出现自我意识之前最需要发展的能力。深度学习目前可以 ... [详细]
  • 都会|可能会_###haohaohao###图神经网络之神器——PyTorch Geometric 上手 & 实战
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了###haohaohao###图神经网络之神器——PyTorchGeometric上手&实战相关的知识,希望对你有一定的参考价值。 ... [详细]
  • C# 三个Timer
    在C#中存在3种常用的这个 ... [详细]
  • 序言Broadcast作为Android的四大组件之一,重要性不言而喻;一般我们使用广播的方式通常如下,继承BroadcastReceiver,新建一个广播类。publicclas ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 眼下准备入手MacbookAir的8GBRam版。 ... [详细]
  • iOS Xcode汇编模式切换的方法介绍
    一、概念 1.汇编指令:模拟器上运行的是Intel指令,而真机上运行的是arm指令, 2.每条汇编指令的格式总是由: 操作码,操作 ... [详细]
author-avatar
mobiledu2502927147
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有